From 46f6efbbf91dbf77b4dcb530dc88ca26138c50b4 Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Wed, 6 Jan 2010 15:21:06 +0000
Subject: [PATCH] LMS-1328 performance of fetching the content of one file from
 DSS: do not fetch dataset metadata, just check the access rights

SVN: 14177
---
 .../etlserver/IdentifiedDataStrategy.java     |  19 +-
 .../generic/server/CommandQueueLister.java    |   3 +-
 .../dss/generic/server/ConfigParameters.java  |   7 +-
 .../server/DatasetDownloadServlet.java        | 200 +++++++++++----
 .../server/EncapsulatedOpenBISService.java    |  11 +-
 .../shared/IEncapsulatedOpenBISService.java   |   8 +-
 .../shared/utils/DatasetLocationUtil.java     |  57 +++++
 .../server/DatasetDownloadServletTest.java    | 232 ++++++++++--------
 .../openbis/generic/server/ETLService.java    |  22 +-
 .../generic/server/ETLServiceLogger.java      |  39 +--
 .../generic/shared/IETLLIMSService.java       |  16 +-
 .../shared/IETLLIMSService.java.expected      |  16 +-
 12 files changed, 420 insertions(+), 210 deletions(-)
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DatasetLocationUtil.java

diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/IdentifiedDataStrategy.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/IdentifiedDataStrategy.java
index b1402dc264e..a46fccb117c 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/IdentifiedDataStrategy.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/IdentifiedDataStrategy.java
@@ -20,8 +20,8 @@ import java.io.File;
 
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
-import ch.systemsx.cisd.common.utilities.MD5ChecksumCalculator;
 import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DatasetLocationUtil;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 
 /**
@@ -64,22 +64,7 @@ public final class IdentifiedDataStrategy implements IDataStoreStrategy
     {
         String dataSetCode = dataSetInfo.getDataSetCode();
         final String instanceUUID = dataSetInfo.getInstanceUUID();
-        final File instanceDir = new File(baseDir, instanceUUID);
-        final File shardingDir = createShardingDir(instanceDir, dataSetCode);
-        final File datasetDir = new File(shardingDir, dataSetCode);
-        return datasetDir;
-    }
-
-    // In order not to overwhelm the file system implementation, we won't use a completely flat
-    // hierarchy, but instead a structure called 'data sharding'. 'Data sharding' ensures that there
-    // are no directories with an extremely large number of data sets.
-    private static File createShardingDir(File parentDir, String dataSetCode)
-    {
-        String checksum = MD5ChecksumCalculator.calculate(dataSetCode);
-        final File dirLevel1 = new File(parentDir, checksum.substring(0, 2));
-        final File dirLevel2 = new File(dirLevel1, checksum.substring(2, 4));
-        final File dirLevel3 = new File(dirLevel2, checksum.substring(4, 6));
-        return dirLevel3;
+        return DatasetLocationUtil.getDatasetLocationPath(baseDir, dataSetCode, instanceUUID);
     }
 
     //
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/CommandQueueLister.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/CommandQueueLister.java
index 6b04bc22482..2a0c14a5970 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/CommandQueueLister.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/CommandQueueLister.java
@@ -16,7 +16,6 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server;
 
-import java.io.File;
 
 /**
  * A class that provides a lister for the commands in the command queue of the store.
@@ -33,6 +32,6 @@ public final class CommandQueueLister
     public static void listQueuedCommand()
     {
         final ConfigParameters configParams = DataStoreServer.getConfigParameters();
-        DataSetCommandExecutor.listQueuedCommands(new File(configParams.getStorePath()));
+        DataSetCommandExecutor.listQueuedCommands(configParams.getStorePath());
     }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ConfigParameters.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ConfigParameters.java
index 362d70a70eb..59fb213febd 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ConfigParameters.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ConfigParameters.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server;
 
+import java.io.File;
 import java.util.Properties;
 
 import org.apache.log4j.Logger;
@@ -52,7 +53,7 @@ final class ConfigParameters
 
     static final String KEYSTORE_KEY_PASSWORD_KEY = KEYSTORE + "key-password";
 
-    private final String storePath;
+    private final File storePath;
 
     private final int port;
 
@@ -73,7 +74,7 @@ final class ConfigParameters
      */
     public ConfigParameters(final Properties properties)
     {
-        storePath = PropertyUtils.getMandatoryProperty(properties, STOREROOT_DIR_KEY);
+        storePath = new File(PropertyUtils.getMandatoryProperty(properties, STOREROOT_DIR_KEY));
         port = getMandatoryIntegerProperty(properties, PORT_KEY);
         serverURL = PropertyUtils.getMandatoryProperty(properties, SERVER_URL_KEY);
         sessionTimeout = getMandatoryIntegerProperty(properties, SESSION_TIMEOUT_KEY) * 60;
@@ -97,7 +98,7 @@ final class ConfigParameters
         }
     }
 
-    public final String getStorePath()
+    public final File getStorePath()
     {
         return storePath;
     }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServlet.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServlet.java
index 7dfd76fca81..24a2ffa78ed 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServlet.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServlet.java
@@ -57,9 +57,10 @@ import ch.systemsx.cisd.common.filesystem.FileUtilities;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DatasetLocationUtil;
 import ch.systemsx.cisd.openbis.dss.generic.shared.utils.ImageUtil;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LocatorType;
 
 /**
  * @author Franz-Josef Elmer
@@ -97,9 +98,11 @@ public class DatasetDownloadServlet extends HttpServlet
 
     private static final String THUMBNAIL_MODE_DISPLAY = "thumbnail";
 
-    static final String DATA_SET_KEY = "data-set";
+    static final String DATABASE_INSTANCE_SESSION_KEY = "database-instance";
 
-    static final String DATASET_CODE_KEY = "dataSetCode";
+    static final String DATA_SET_ACCESS_SESSION_KEY = "data-set-access";
+
+    static final String DATA_SET_SESSION_KEY = "data-set";
 
     static final String SESSION_ID_KEY = "sessionID";
 
@@ -237,10 +240,7 @@ public class DatasetDownloadServlet extends HttpServlet
                     parseRequestURL(request, DATA_STORE_SERVER_WEB_APPLICATION_NAME);
             rendererFactory = createRendererFactory(requestParams.getDisplayMode());
 
-            obtainDataSetFromServer(requestParams.getDataSetCode(),
-                    requestParams.tryGetSessionId(), request);
-
-            HttpSession session = request.getSession(false);
+            HttpSession session = tryGetOrCreateSession(request, requestParams);
             if (session == null)
             {
                 printSessionExpired(response);
@@ -263,16 +263,18 @@ public class DatasetDownloadServlet extends HttpServlet
             throws UnsupportedEncodingException, IOException
     {
         String dataSetCode = requestParams.getDataSetCode();
-        ExternalData dataSet = tryToGetDataSet(session, dataSetCode);
-        if (dataSet == null)
-        {
-            throw new UserFailureException("Unknown data set '" + dataSetCode + "'.");
-        }
-        File rootDir = createDataSetRootDirectory(dataSet);
+        RenderingContext context = createRenderingContext(requestParams, dataSetCode, session);
+        renderPage(rendererFactory, response, dataSetCode, context, requestParams, session);
+    }
+
+    private RenderingContext createRenderingContext(RequestParams requestParams,
+            String dataSetCode, HttpSession session)
+    {
+        File rootDir = createDataSetRootDirectory(dataSetCode, session);
         RenderingContext context =
                 new RenderingContext(rootDir, requestParams.getURLPrefix(), requestParams
                         .getPathInfo());
-        renderPage(rendererFactory, response, dataSet, context, requestParams);
+        return context;
     }
 
     private IRendererFactory createRendererFactory(String displayMode)
@@ -351,20 +353,29 @@ public class DatasetDownloadServlet extends HttpServlet
     }
 
     private void renderPage(IRendererFactory rendererFactory, HttpServletResponse response,
-            ExternalData dataSet, RenderingContext renderingContext, RequestParams requestParams)
-            throws IOException
+            String dataSetCode, RenderingContext renderingContext, RequestParams requestParams,
+            HttpSession session) throws IOException
     {
         File file = renderingContext.getFile();
         if (file.exists() == false)
         {
             throw new EnvironmentFailureException("File '" + file.getName() + "' does not exist.");
         }
+        // If we want to browse a directory, we need a whole dataset metadata from openbis to
+        // display them for the user. But if just a file is needed, then it's much faster to just
+        // check the access rights in openbis.
+        String sessionIdOrNull = requestParams.tryGetSessionId();
         if (file.isDirectory())
         {
+            ExternalData dataSet = getDataSet(dataSetCode, sessionIdOrNull, session);
             createPage(rendererFactory, response, dataSet, renderingContext, file);
         } else
         {
-            deliverFile(response, dataSet, file, requestParams.getDisplayMode());
+            if (isDatasetAccessible(dataSetCode, sessionIdOrNull, session) == false)
+            {
+                throw new UserFailureException("Data set '" + dataSetCode + "' is not accessible.");
+            }
+            deliverFile(response, dataSetCode, file, requestParams.getDisplayMode());
         }
     }
 
@@ -415,7 +426,7 @@ public class DatasetDownloadServlet extends HttpServlet
         }
     }
 
-    private void deliverFile(final HttpServletResponse response, ExternalData dataSet, File file,
+    private void deliverFile(final HttpServletResponse response, String dataSetCode, File file,
             String displayMode) throws IOException, FileNotFoundException
     {
         ServletOutputStream outputStream = null;
@@ -445,7 +456,7 @@ public class DatasetDownloadServlet extends HttpServlet
         }
         if (operationLog.isInfoEnabled())
         {
-            operationLog.info("For data set '" + dataSet.getCode() + "' deliver file "
+            operationLog.info("For data set '" + dataSetCode + "' deliver file "
                     + file.getAbsolutePath() + infoPostfix);
         }
         response.setContentLength(size);
@@ -491,53 +502,137 @@ public class DatasetDownloadServlet extends HttpServlet
         writer.close();
     }
 
-    private void obtainDataSetFromServer(String dataSetCode, String sessionIdOrNull,
-            final HttpServletRequest request)
+    private ExternalData getDataSet(String dataSetCode, String sessionIdOrNull, HttpSession session)
+    {
+        ExternalData dataset = tryToGetCachedDataSet(session, dataSetCode);
+        if (dataset != null)
+        {
+            return dataset;
+        }
+        ensureSessionIdSpecified(sessionIdOrNull);
+        ExternalData dataSet = tryGetDataSetFromServer(dataSetCode, sessionIdOrNull);
+        if (dataSet != null)
+        {
+            putDataSetToMap(session, dataSetCode, dataSet);
+            return dataSet;
+        } else
+        {
+            throw new UserFailureException("Unknown data set '" + dataSetCode + "'.");
+        }
+    }
+
+    private void ensureSessionIdSpecified(String sessionIdOrNull)
+    {
+        if (sessionIdOrNull == null)
+        {
+            throw new EnvironmentFailureException("Session id not specified in the URL");
+        }
+    }
+
+    private ExternalData tryGetDataSetFromServer(String dataSetCode, String sessionIdOrNull)
+    {
+        IEncapsulatedOpenBISService dataSetService = applicationContext.getDataSetService();
+        ExternalData dataSet = dataSetService.tryGetDataSet(sessionIdOrNull, dataSetCode);
+        if (operationLog.isInfoEnabled())
+        {
+            String actionDesc = (dataSet != null) ? "obtained from" : "not found in";
+            operationLog.info(String.format("Data set '%s' %s openBIS server.", dataSetCode,
+                    actionDesc));
+        }
+        return dataSet;
+    }
+
+    private HttpSession tryGetOrCreateSession(final HttpServletRequest request,
+            RequestParams requestParams)
     {
         HttpSession session = request.getSession(false);
-        if (session != null && tryToGetDataSet(session, dataSetCode) != null)
+        if (session == null && requestParams.tryGetSessionId() == null)
         {
-            return; // dataset is already in the cache
+            // a) The session is expired and b) we do not have openbis session id provided.
+            // So a) metadata about datasets are not in the session and b) we cannot get them from
+            // openbis.
+            return null;
         }
-        if (sessionIdOrNull != null)
+        if (session == null)
         {
-            IEncapsulatedOpenBISService dataSetService = applicationContext.getDataSetService();
-            ExternalData dataSet = dataSetService.tryGetDataSet(sessionIdOrNull, dataSetCode);
-            if (operationLog.isInfoEnabled())
+            session = request.getSession(true);
+            ConfigParameters configParameters = applicationContext.getConfigParameters();
+            session.setMaxInactiveInterval(configParameters.getSessionTimeout());
+        }
+        return session;
+    }
+
+    private File createDataSetRootDirectory(String dataSetCode, HttpSession session)
+    {
+        DatabaseInstance databaseInstance = getDatabaseInstance(session);
+        File storeDir = applicationContext.getConfigParameters().getStorePath();
+        File dataSetRootDirectory =
+                DatasetLocationUtil.getDatasetLocationPath(storeDir, dataSetCode, databaseInstance
+                        .getUuid());
+        if (dataSetRootDirectory.exists() == false)
+        {
+            throw new UserFailureException("Data set '" + dataSetCode + "' not found in the store.");
+        }
+        return dataSetRootDirectory;
+    }
+
+    // ---
+
+    private DatabaseInstance getDatabaseInstance(HttpSession session)
+    {
+        DatabaseInstance databaseInstance =
+                (DatabaseInstance) session.getAttribute(DATABASE_INSTANCE_SESSION_KEY);
+        if (databaseInstance == null)
+        {
+            databaseInstance = applicationContext.getDataSetService().getHomeDatabaseInstance();
+            session.setAttribute(DATABASE_INSTANCE_SESSION_KEY, databaseInstance);
+        }
+        return databaseInstance;
+    }
+
+    // ---
+
+    private boolean isDatasetAccessible(String dataSetCode, String sessionIdOrNull,
+            HttpSession session)
+    {
+        Boolean access = getDataSetAccess(session).get(dataSetCode);
+        if (access == null)
+        {
+            if (tryToGetCachedDataSet(session, dataSetCode) != null)
             {
-                String actionDesc = (dataSet != null) ? "obtained from" : "not found in";
-                operationLog.info(String.format("Data set '%s' %s openBIS server.", dataSetCode,
-                        actionDesc));
+                return true; // access already checked and granted
             }
-
-            ConfigParameters configParameters = applicationContext.getConfigParameters();
-            if (session == null)
+            if (operationLog.isInfoEnabled())
             {
-                session = request.getSession(true);
-                session.setMaxInactiveInterval(configParameters.getSessionTimeout());
+                operationLog.info(String.format(
+                        "Check access to the data set '%s' at openBIS server.", dataSetCode));
             }
-            if (dataSet != null)
+            IEncapsulatedOpenBISService dataSetService = applicationContext.getDataSetService();
+            ensureSessionIdSpecified(sessionIdOrNull);
+            try
+            {
+                dataSetService.checkDataSetAccess(sessionIdOrNull, dataSetCode);
+                access = true;
+            } catch (UserFailureException ex)
             {
-                putDataSetToMap(session, dataSetCode, dataSet);
+                access = false;
             }
+            getDataSetAccess(session).put(dataSetCode, access);
         }
+        return access;
     }
 
-    private File createDataSetRootDirectory(ExternalData dataSet)
+    @SuppressWarnings("unchecked")
+    private Map<String, Boolean> getDataSetAccess(HttpSession session)
     {
-        String path = dataSet.getLocation();
-        LocatorType locatorType = dataSet.getLocatorType();
-        if (locatorType.getCode().equals(LocatorType.DEFAULT_LOCATOR_TYPE_CODE))
-        {
-            path = applicationContext.getConfigParameters().getStorePath() + "/" + path;
-        }
-        File dataSetRootDirectory = new File(path);
-        if (dataSetRootDirectory.exists() == false)
+        Map<String, Boolean> map =
+                (Map<String, Boolean>) session.getAttribute(DATA_SET_ACCESS_SESSION_KEY);
+        if (map == null)
         {
-            throw new UserFailureException("Data set '" + dataSet.getCode()
-                    + "' not found in store at '" + dataSetRootDirectory.getAbsolutePath() + "'.");
+            map = new HashMap<String, Boolean>();
+            session.setAttribute(DATA_SET_ACCESS_SESSION_KEY, map);
         }
-        return dataSetRootDirectory;
+        return map;
     }
 
     private void putDataSetToMap(HttpSession session, String dataSetCode, ExternalData dataSet)
@@ -545,7 +640,7 @@ public class DatasetDownloadServlet extends HttpServlet
         getDataSets(session).put(dataSetCode, dataSet);
     }
 
-    private ExternalData tryToGetDataSet(HttpSession session, String dataSetCode)
+    private ExternalData tryToGetCachedDataSet(HttpSession session, String dataSetCode)
     {
         return getDataSets(session).get(dataSetCode);
     }
@@ -554,13 +649,12 @@ public class DatasetDownloadServlet extends HttpServlet
     private Map<String, ExternalData> getDataSets(HttpSession session)
     {
         Map<String, ExternalData> map =
-                (Map<String, ExternalData>) session.getAttribute(DATA_SET_KEY);
+                (Map<String, ExternalData>) session.getAttribute(DATA_SET_SESSION_KEY);
         if (map == null)
         {
             map = new HashMap<String, ExternalData>();
-            session.setAttribute(DATA_SET_KEY, map);
+            session.setAttribute(DATA_SET_SESSION_KEY, map);
         }
         return map;
     }
-
 }
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 306d9fa496a..ae7b6e0fa10 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
@@ -297,7 +297,7 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
             return service.getSampleType(sessionToken, sampleTypeCode);
         }
     }
-    
+
     synchronized public DataSetTypeWithVocabularyTerms getDataSetType(String dataSetTypeCode)
     {
         checkSessionToken();
@@ -324,7 +324,7 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
             authenticate();
             return service.listDataSetsBySampleID(sessionToken, id, showOnlyDirectlyConnected);
         }
-    }    
+    }
 
     synchronized public long registerSample(NewSample newSample) throws UserFailureException
     {
@@ -433,6 +433,13 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer
         return service.tryGetDataSet(sToken, dataSetCode);
     }
 
+    synchronized public void checkDataSetAccess(String sToken, String dataSetCode)
+            throws UserFailureException
+    {
+        checkSessionToken();
+        service.checkDataSetAccess(sToken, dataSetCode);
+    }
+
     synchronized public List<SimpleDataSetInformationDTO> listDataSets()
             throws UserFailureException
     {
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 bc43254b797..e438ec76c20 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
@@ -52,6 +52,12 @@ public interface IEncapsulatedOpenBISService
     public ExternalData tryGetDataSet(final String sessionToken, final String dataSetCode)
             throws UserFailureException;
 
+    /**
+     * Checks if the current user has access rights to a dataset with the specified data set code.
+     */
+    public void checkDataSetAccess(final String sessionToken, final String dataSetCode)
+            throws UserFailureException;
+
     /**
      * Tries to get the experiment of specified identifier or <code>null</code> if not found.
      */
@@ -91,7 +97,7 @@ public interface IEncapsulatedOpenBISService
      */
     public List<ExternalData> listDataSetsBySampleID(long sampleID,
             boolean showOnlyDirectlyConnected) throws UserFailureException;
-    
+
     /**
      * Registers the specified sample.
      * 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DatasetLocationUtil.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DatasetLocationUtil.java
new file mode 100644
index 00000000000..575c17a7ab6
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DatasetLocationUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.dss.generic.shared.utils;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.utilities.MD5ChecksumCalculator;
+
+/**
+ * @author Tomasz Pylak
+ */
+public class DatasetLocationUtil
+{
+    /** Creates a location where a dataset can be found in a specified base directory. */
+    public static File getDatasetLocationPath(final File baseDir, String dataSetCode,
+            final String instanceUUID)
+    {
+        return new File(baseDir, getDatasetRelativeLocationPath(dataSetCode, instanceUUID));
+    }
+
+    /** returns path relative to the store */
+    public static String getDatasetRelativeLocationPath(String dataSetCode,
+            final String instanceUUID)
+    {
+        final File instanceDir = new File(instanceUUID);
+        final File shardingDir = createShardingDir(instanceDir, dataSetCode);
+        final File datasetDir = new File(shardingDir, dataSetCode);
+        return datasetDir.getPath();
+    }
+
+    // In order not to overwhelm the file system implementation, we won't use a completely flat
+    // hierarchy, but instead a structure called 'data sharding'. 'Data sharding' ensures that there
+    // are no directories with an extremely large number of data sets.
+    private static File createShardingDir(File parentDir, String dataSetCode)
+    {
+        String checksum = MD5ChecksumCalculator.calculate(dataSetCode);
+        final File dirLevel1 = new File(parentDir, checksum.substring(0, 2));
+        final File dirLevel2 = new File(dirLevel1, checksum.substring(2, 4));
+        final File dirLevel3 = new File(dirLevel2, checksum.substring(4, 6));
+        return dirLevel3;
+    }
+
+}
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServletTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServletTest.java
index 3ce565f8ae1..d6b22c8a07d 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServletTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/DatasetDownloadServletTest.java
@@ -50,7 +50,8 @@ import ch.systemsx.cisd.base.utilities.OSUtilities;
 import ch.systemsx.cisd.common.filesystem.FileUtilities;
 import ch.systemsx.cisd.common.logging.BufferedAppender;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
-import ch.systemsx.cisd.openbis.generic.shared.GenericSharedConstants;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DatasetLocationUtil;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Group;
@@ -58,7 +59,6 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LocatorType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 
 /**
  * @author Franz-Josef Elmer
@@ -77,12 +77,14 @@ public class DatasetDownloadServletTest
 
     private static final String LOG_ERROR = "ERROR " + LOGGER_NAME + " - ";
 
+    private static final String DATABASE_INSTANCE_UUID = "db-uuid";
+
     private static final File TEST_FOLDER = new File("targets/unit-test/store");
 
-    private static final String EXAMPLE_DATA_SET_FOLDER_NAME = "data set #123";
+    private static final String EXAMPLE_DATA_SET_CODE = "1234-1";
 
     private static final File EXAMPLE_DATA_SET_FOLDER =
-            new File(TEST_FOLDER, EXAMPLE_DATA_SET_FOLDER_NAME);
+            getDatasetDirectoryLocation(TEST_FOLDER, EXAMPLE_DATA_SET_CODE);
 
     private static final String EXAMPLE_FILE_NAME = "read me @home.txt";
 
@@ -100,8 +102,6 @@ public class DatasetDownloadServletTest
 
     private static final String EXAMPLE_SESSION_ID = "AV76CF";
 
-    private static final String EXAMPLE_DATA_SET_CODE = "1234-1";
-
     private static final String SAMPLE_CODE = "SAMPLE-S";
 
     private static final String EXPERIMENT_CODE = "EPERIMENT-E";
@@ -144,7 +144,7 @@ public class DatasetDownloadServletTest
         dataSetService = context.mock(IEncapsulatedOpenBISService.class);
         httpSession = context.mock(HttpSession.class);
         TEST_FOLDER.mkdirs();
-        EXAMPLE_DATA_SET_FOLDER.mkdir();
+        EXAMPLE_DATA_SET_FOLDER.mkdirs();
         FileUtilities.writeToFile(EXAMPLE_FILE, EXAMPLE_FILE_CONTENT);
         EXAMPLE_DATA_SET_SUB_FOLDER.mkdir();
     }
@@ -199,7 +199,7 @@ public class DatasetDownloadServletTest
                         + OSUtilities.LINE_SEPARATOR + "", writer.toString());
         assertEquals(LOG_INFO + "Data set '1234-1' obtained from openBIS server."
                 + OSUtilities.LINE_SEPARATOR + LOG_INFO
-                + "For data set '1234-1' show directory <wd>/data set #123",
+                + "For data set '1234-1' show directory <wd>/db-uuid/0a/28/59/1234-1",
                 getNormalizedLogContent());
 
         context.assertIsSatisfied();
@@ -234,7 +234,7 @@ public class DatasetDownloadServletTest
                         + OSUtilities.LINE_SEPARATOR + "", writer.toString());
         assertEquals(LOG_INFO + "Data set '1234-1' obtained from openBIS server."
                 + OSUtilities.LINE_SEPARATOR + LOG_INFO
-                + "For data set '1234-1' show directory <wd>/data set #123",
+                + "For data set '1234-1' show directory <wd>/db-uuid/0a/28/59/1234-1",
                 getNormalizedLogContent());
 
         context.assertIsSatisfied();
@@ -259,15 +259,18 @@ public class DatasetDownloadServletTest
     {
         final StringWriter writer = new StringWriter();
         final ExternalData externalData = createExternalData();
-        LocatorType locatorType = new LocatorType();
-        locatorType.setCode("unknown");
-        externalData.setLocatorType(locatorType);
         prepareParseRequestURL();
-        prepareForObtainingDataSetFromServer(externalData);
+        prepareCreateSession();
         context.checking(new Expectations()
             {
                 {
-                    prepareForGettingDataSetFromSession(this, externalData, "blabla");
+                    prepareGetRequestURI(this, externalData, "blabla");
+
+                    one(request).getRequestURL();
+                    will(returnValue(new StringBuffer("requestURL")));
+
+                    one(request).getQueryString();
+                    will(returnValue("queryString"));
 
                     one(response).setContentType("text/html");
                     one(response).getWriter();
@@ -278,7 +281,7 @@ public class DatasetDownloadServletTest
         DatasetDownloadServlet servlet = createServlet();
         servlet.doGet(request, response);
         String pageContent = writer.toString();
-        String snippet = "Data set '1234-1' not found in store";
+        String snippet = "File 'blabla' does not exist.";
         assertEquals("Text snippet >" + snippet + "< not found in following page content: "
                 + pageContent, true, pageContent.indexOf(snippet) > 0);
         String logContent = logRecorder.getLogContent();
@@ -292,22 +295,14 @@ public class DatasetDownloadServletTest
     public void testDoGetButUnknownDataSetCode() throws Exception
     {
         final StringWriter writer = new StringWriter();
-        final ExternalData externalData = createExternalData();
         prepareParseRequestURL();
-        prepareForObtainingDataSetFromServer(externalData);
+        prepareCreateSession();
+        prepareTryGetDataset(null);
         context.checking(new Expectations()
             {
                 {
-                    one(request).getSession(false);
-                    will(returnValue(httpSession));
-
-                    one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_KEY);
-                    Map<String, NewExternalData> map = new HashMap<String, NewExternalData>();
-                    will(returnValue(map));
-
                     one(request).getRequestURI();
-                    String codeAndPath = externalData.getCode();
-                    will(returnValue(REQUEST_URI_PREFIX + codeAndPath));
+                    will(returnValue(REQUEST_URI_PREFIX + EXAMPLE_DATA_SET_CODE));
 
                     one(response).setContentType("text/html");
                     one(response).getWriter();
@@ -321,7 +316,7 @@ public class DatasetDownloadServletTest
                 + "Unknown data set '1234-1'." + OSUtilities.LINE_SEPARATOR + "</body></html>"
                 + OSUtilities.LINE_SEPARATOR, writer.toString());
         String logContent = logRecorder.getLogContent();
-        assertEquals(LOG_INFO + "Data set '1234-1' obtained from openBIS server."
+        assertEquals(LOG_INFO + "Data set '1234-1' not found in openBIS server."
                 + OSUtilities.LINE_SEPARATOR + LOG_INFO
                 + "User failure: Unknown data set '1234-1'.", logContent);
 
@@ -338,8 +333,13 @@ public class DatasetDownloadServletTest
                 {
                     prepareParseRequestURLNoSession(this);
 
-                    one(request).getSession(false);
-                    will(returnValue(null));
+                    allowing(request).getSession(false);
+                    will(returnValue(httpSession));
+
+                    DatabaseInstance databaseInstance = new DatabaseInstance();
+                    databaseInstance.setUuid(DATABASE_INSTANCE_UUID);
+                    getSessionAttribute(this, DatasetDownloadServlet.DATABASE_INSTANCE_SESSION_KEY,
+                            databaseInstance);
 
                     prepareForGettingDataSetFromSession(this, externalData,
                             ESCAPED_EXAMPLE_DATA_SET_SUB_FOLDER_NAME);
@@ -363,7 +363,8 @@ public class DatasetDownloadServletTest
                         + OSUtilities.LINE_SEPARATOR
                         + "</table> </div> <div class=\'footer\'>Copyright &copy; 2008 ETHZ - <a href=\'http://www.cisd.systemsx.ethz.ch/\'>CISD</a> </div> </body></html>"
                         + OSUtilities.LINE_SEPARATOR, writer.toString());
-        assertEquals(LOG_INFO + "For data set '1234-1' show directory <wd>/data set #123/"
+        assertEquals(LOG_INFO
+                + "For data set '1234-1' show directory <wd>/db-uuid/0a/28/59/1234-1/"
                 + EXAMPLE_DATA_SET_SUB_FOLDER_NAME, getNormalizedLogContent());
 
         context.assertIsSatisfied();
@@ -375,15 +376,13 @@ public class DatasetDownloadServletTest
         final ExternalData externalData = createExternalData();
 
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        prepareCreateSession();
+        prepareCheckDatasetAccess();
+        prepareParseRequestURL();
         context.checking(new Expectations()
             {
                 {
-                    prepareParseRequestURLNoSession(this);
-
-                    one(request).getSession(false);
-                    will(returnValue(null));
-
-                    prepareForGettingDataSetFromSession(this, externalData, EXAMPLE_FILE_NAME);
+                    prepareGetRequestURI(this, externalData, EXAMPLE_FILE_NAME);
 
                     one(response).setContentType("text/plain");
                     one(response).setContentLength(EXAMPLE_FILE_CONTENT.length());
@@ -404,8 +403,10 @@ public class DatasetDownloadServletTest
         DatasetDownloadServlet servlet = createServlet();
         servlet.doGet(request, response);
         assertEquals("Hello world!", outputStream.toString());
-        assertEquals(LOG_INFO + "For data set '1234-1' deliver file "
-                + "<wd>/data set #123/read me @home.txt (12 bytes).", getNormalizedLogContent());
+        assertEquals(LOG_INFO + "Check access to the data set '1234-1' at openBIS server."
+                + OSUtilities.LINE_SEPARATOR + LOG_INFO + "For data set '1234-1' deliver file "
+                + "<wd>/db-uuid/0a/28/59/1234-1/read me @home.txt (12 bytes).",
+                getNormalizedLogContent());
 
         context.assertIsSatisfied();
     }
@@ -415,9 +416,9 @@ public class DatasetDownloadServletTest
     {
         BufferedImage image = new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB);
         ImageIO.write(image, "png", EXAMPLE_FILE);
-        final ExternalData externalData = createExternalData();
         prepareParseRequestURLForThumbnail(100, 50);
-        prepareForObtainingDataSetFromServer(externalData);
+        prepareCreateSession();
+        prepareCheckDatasetAccess();
         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         context.checking(new Expectations()
             {
@@ -426,14 +427,6 @@ public class DatasetDownloadServletTest
                     will(returnValue(REQUEST_URI_PREFIX + EXAMPLE_DATA_SET_CODE + "/"
                             + EXAMPLE_FILE_NAME));
 
-                    one(request).getSession(false);
-                    will(returnValue(httpSession));
-
-                    one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_KEY);
-                    Map<String, ExternalData> map = new HashMap<String, ExternalData>();
-                    map.put(externalData.getCode(), externalData);
-                    will(returnValue(map));
-
                     one(response).setContentType("image/png");
                     one(response).setContentLength(84);
                     one(response).setHeader("Content-Disposition",
@@ -456,10 +449,13 @@ public class DatasetDownloadServletTest
                 ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
         assertEquals(25, thumbnail.getWidth());
         assertEquals(50, thumbnail.getHeight());
-        assertEquals(LOG_INFO + "Data set '1234-1' obtained from openBIS server."
-                + OSUtilities.LINE_SEPARATOR + LOG_INFO
-                + "For data set '1234-1' deliver file <wd>/data set #123/read me @home.txt "
-                + "as a thumbnail.", getNormalizedLogContent());
+        assertEquals(
+                LOG_INFO
+                        + "Check access to the data set '1234-1' at openBIS server."
+                        + OSUtilities.LINE_SEPARATOR
+                        + LOG_INFO
+                        + "For data set '1234-1' deliver file <wd>/db-uuid/0a/28/59/1234-1/read me @home.txt "
+                        + "as a thumbnail.", getNormalizedLogContent());
 
         context.assertIsSatisfied();
     }
@@ -469,15 +465,12 @@ public class DatasetDownloadServletTest
     {
         final StringWriter writer = new StringWriter();
         final ExternalData externalData = createExternalData();
+        prepareParseRequestURL();
+        prepareCreateSession();
         context.checking(new Expectations()
             {
                 {
-                    prepareParseRequestURLNoSession(this);
-
-                    one(request).getSession(false);
-                    will(returnValue(null));
-
-                    prepareForGettingDataSetFromSession(this, externalData, "blabla");
+                    prepareGetRequestURI(this, externalData, "blabla");
 
                     one(request).getRequestURL();
                     will(returnValue(new StringBuffer("requestURL")));
@@ -566,36 +559,6 @@ public class DatasetDownloadServletTest
         context.assertIsSatisfied();
     }
 
-    @Test
-    public void testDoGetForPathInfoStartingWithSeparator() throws Exception
-    {
-        final StringWriter writer = new StringWriter();
-        final ExternalData externalData = createExternalData();
-        prepareParseRequestURL();
-        prepareForObtainingDataSetFromServer(externalData);
-        context.checking(new Expectations()
-            {
-                {
-                    one(request).getRequestURI();
-                    will(returnValue(REQUEST_URI_PREFIX + EXAMPLE_DATA_SET_CODE));
-
-                    one(request).getSession(false);
-                    will(returnValue(null));
-
-                    one(response).getWriter();
-                    will(returnValue(new PrintWriter(writer)));
-                }
-            });
-
-        DatasetDownloadServlet servlet = createServlet();
-        servlet.doGet(request, response);
-        assertEquals(EXPIRATION_MESSAGE, writer.toString());
-        assertEquals(LOG_INFO + "Data set '1234-1' obtained from openBIS server.",
-                getNormalizedLogContent());
-
-        context.assertIsSatisfied();
-    }
-
     private void prepareForGettingDataSetFromSession(final ExternalData externalData,
             final String path)
     {
@@ -610,23 +573,20 @@ public class DatasetDownloadServletTest
     private void prepareForGettingDataSetFromSession(Expectations exp,
             final ExternalData externalData, final String path)
     {
-        exp.one(request).getSession(false);
-        exp.will(Expectations.returnValue(httpSession));
-
-        exp.one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_KEY);
+        exp.one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_SESSION_KEY);
         Map<String, ExternalData> map = new HashMap<String, ExternalData>();
         map.put(externalData.getCode(), externalData);
         exp.will(Expectations.returnValue(map));
 
+        prepareGetRequestURI(exp, externalData, path);
+    }
+
+    private void prepareGetRequestURI(Expectations exp, final ExternalData externalData,
+            final String path)
+    {
         exp.one(request).getRequestURI();
         String codeAndPath = REQUEST_URI_PREFIX + externalData.getCode() + "/" + path;
         exp.will(Expectations.returnValue(codeAndPath));
-
-        exp.allowing(request).getRequestURI();
-        exp.will(Expectations
-                .returnValue(GenericSharedConstants.DATA_STORE_SERVER_WEB_APPLICATION_NAME + "/"
-                        + codeAndPath));
-
     }
 
     private void prepareParseRequestURLNoSession(Expectations exp)
@@ -653,29 +613,82 @@ public class DatasetDownloadServletTest
     }
 
     private void prepareForObtainingDataSetFromServer(final ExternalData externalData)
+    {
+        prepareCreateSession();
+        prepareTryGetDataset(externalData);
+    }
+
+    private void prepareCreateSession()
     {
         context.checking(new Expectations()
             {
                 {
-                    one(dataSetService).tryGetDataSet(EXAMPLE_SESSION_ID, EXAMPLE_DATA_SET_CODE);
-                    will(returnValue(externalData));
-
-                    one(request).getSession(false);
+                    allowing(request).getSession(false);
                     will(returnValue(null));
 
                     one(request).getSession(true);
                     will(returnValue(httpSession));
 
                     one(httpSession).setMaxInactiveInterval(120);
-                    one(httpSession).getAttribute(DatasetDownloadServlet.DATA_SET_KEY);
-                    will(returnValue(null));
 
-                    one(httpSession).setAttribute(DatasetDownloadServlet.DATA_SET_KEY,
+                    DatabaseInstance databaseInstance = new DatabaseInstance();
+                    databaseInstance.setUuid(DATABASE_INSTANCE_UUID);
+                    one(dataSetService).getHomeDatabaseInstance();
+                    will(returnValue(databaseInstance));
+                    checkAndSetAttribute(this,
+                            DatasetDownloadServlet.DATABASE_INSTANCE_SESSION_KEY, databaseInstance);
+
+                }
+            });
+    }
+
+    private void prepareTryGetDataset(final ExternalData externalData)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    HashMap<String, ExternalDataPE> map = new HashMap<String, ExternalDataPE>();
+                    checkAndSetAttribute(this, DatasetDownloadServlet.DATA_SET_SESSION_KEY, map);
+
+                    one(dataSetService).tryGetDataSet(EXAMPLE_SESSION_ID, EXAMPLE_DATA_SET_CODE);
+                    will(returnValue(externalData));
+                }
+            });
+    }
+
+    private void prepareCheckDatasetAccess()
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    getSessionAttribute(this, DatasetDownloadServlet.DATA_SET_SESSION_KEY,
                             new HashMap<String, ExternalDataPE>());
+
+                    Map<String, Boolean> map = new HashMap<String, Boolean>();
+                    checkAndSetAttribute(this, DatasetDownloadServlet.DATA_SET_ACCESS_SESSION_KEY,
+                            map);
+
+                    one(dataSetService).checkDataSetAccess(EXAMPLE_SESSION_ID,
+                            EXAMPLE_DATA_SET_CODE);
+
+                    getSessionAttribute(this, DatasetDownloadServlet.DATA_SET_ACCESS_SESSION_KEY,
+                            map);
                 }
             });
     }
 
+    private void checkAndSetAttribute(Expectations exp, String attributeKey, Object newValue)
+    {
+        getSessionAttribute(exp, attributeKey, null);
+        exp.one(httpSession).setAttribute(attributeKey, newValue);
+    }
+
+    private void getSessionAttribute(Expectations exp, String attributeKey, Object value)
+    {
+        exp.one(httpSession).getAttribute(attributeKey);
+        exp.will(Expectations.returnValue(value));
+    }
+
     private void prepareForCreatingHTML(final StringWriter writer) throws IOException
     {
         context.checking(new Expectations()
@@ -716,10 +729,17 @@ public class DatasetDownloadServletTest
         LocatorType locatorType = new LocatorType();
         locatorType.setCode(LocatorType.DEFAULT_LOCATOR_TYPE_CODE);
         externalData.setLocatorType(locatorType);
-        externalData.setLocation(EXAMPLE_DATA_SET_FOLDER_NAME);
+        externalData.setLocation(DatasetLocationUtil.getDatasetRelativeLocationPath(
+                EXAMPLE_DATA_SET_CODE, DATABASE_INSTANCE_UUID));
         return externalData;
     }
 
+    private static File getDatasetDirectoryLocation(final File baseDir, String dataSetCode)
+    {
+        return DatasetLocationUtil.getDatasetLocationPath(baseDir, dataSetCode,
+                DATABASE_INSTANCE_UUID);
+    }
+
     private DatasetDownloadServlet createServlet()
     {
         Properties properties = new Properties();
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 e405d8c8574..ab333e57df4 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
@@ -345,12 +345,13 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
             throws UserFailureException
     {
         checkSession(sessionToken);
-        
+
         ISampleTypeDAO sampleTypeDAO = getDAOFactory().getSampleTypeDAO();
         SampleTypePE sampleType = sampleTypeDAO.tryFindSampleTypeByCode(sampleTypeCode);
         if (sampleType == null)
         {
-            throw new UserFailureException("No sample type found with code '" + sampleTypeCode + "'.");
+            throw new UserFailureException("No sample type found with code '" + sampleTypeCode
+                    + "'.");
         }
         HibernateUtils.initialize(sampleType.getSampleTypePropertyTypes());
         return SampleTypeTranslator.translate(sampleType, null);
@@ -360,16 +361,18 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
             throws UserFailureException
     {
         checkSession(sessionToken);
-        
+
         IDataSetTypeDAO dataSetTypeDAO = getDAOFactory().getDataSetTypeDAO();
         DataSetTypePE dataSetType = dataSetTypeDAO.tryToFindDataSetTypeByCode(dataSetTypeCode);
         if (dataSetType == null)
         {
-            throw new UserFailureException("No data set type found with code '" + dataSetTypeCode + "'.");
+            throw new UserFailureException("No data set type found with code '" + dataSetTypeCode
+                    + "'.");
         }
         DataSetTypeWithVocabularyTerms result = new DataSetTypeWithVocabularyTerms();
         result.setCode(dataSetType.getCode());
-        Set<DataSetTypePropertyTypePE> dataSetTypePropertyTypes = dataSetType.getDataSetTypePropertyTypes();
+        Set<DataSetTypePropertyTypePE> dataSetTypePropertyTypes =
+                dataSetType.getDataSetTypePropertyTypes();
         HibernateUtils.initialize(dataSetTypePropertyTypes);
         for (DataSetTypePropertyTypePE dataSetTypePropertyTypePE : dataSetTypePropertyTypes)
         {
@@ -387,7 +390,7 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         }
         return result;
     }
-    
+
     public List<ExternalData> listDataSetsBySampleID(final String sessionToken,
             final TechId sampleId, final boolean showOnlyDirectlyConnected)
     {
@@ -502,6 +505,13 @@ public class ETLService extends AbstractCommonServer<IETLService> implements IET
         return ExternalDataTranslator.translate(externalDataBO.getExternalData(),
                 dataStoreBaseURLProvider.getDataStoreBaseURL(), session.getBaseIndexURL());
     }
+    
+    public void checkDataSetAccess(String sessionToken, String dataSetCode)
+            throws UserFailureException
+    {
+        checkSession(sessionToken);
+        // do nothing, the access rights specified in method annotations are checked by a proxy
+    }
 
     public List<Sample> listSamplesByCriteria(String sessionToken,
             ListSamplesByPropertyCriteria criteria) throws UserFailureException
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 ad5db863c47..ae159cbc323 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
@@ -43,7 +43,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.GroupIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
 
 /**
- * @author     Franz-Josef Elmer
+ * @author Franz-Josef Elmer
  */
 public class ETLServiceLogger extends AbstractServerLogger implements IETLService
 {
@@ -56,7 +56,7 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
 
     public String createDataSetCode(String sessionToken) throws UserFailureException
     {
-        logTracking(sessionToken, "create_data_set_code", "");
+        logTracking(sessionToken, "createDataSetCode", "");
         return null;
     }
 
@@ -74,7 +74,7 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
         DatastoreServiceDescriptions services = info.getServicesDescriptions();
         logTracking(
                 sessionToken,
-                "register_data_store_server_session_token",
+                "registerDataStoreServer",
                 "CODE(%s) DOWNLOAD-URL(%s) PORT(%s) DSS-TOKEN(%s) REPORTING_PLUGINS(%s), PROCESSING_PLUGINS(%s)",
                 code, downloadUrl, port, dssSessionToken, services
                         .getReportingServiceDescriptions(), services
@@ -84,7 +84,7 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
     public long registerSample(String sessionToken, NewSample newSample)
             throws UserFailureException
     {
-        logTracking(sessionToken, "register_sample", "SAMPLE_TYPE(%s) SAMPLE(%S)", newSample
+        logTracking(sessionToken, "registerSample", "SAMPLE_TYPE(%s) SAMPLE(%S)", newSample
                 .getSampleType(), newSample.getIdentifier());
         return 0;
     }
@@ -92,71 +92,78 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
     public void registerDataSet(String sessionToken, SampleIdentifier sampleIdentifier,
             NewExternalData externalData) throws UserFailureException
     {
-        logTracking(sessionToken, "register_data_set", "SAMPLE(%s) DATA_SET(%s)", sampleIdentifier,
+        logTracking(sessionToken, "registerDataSet", "SAMPLE(%s) DATA_SET(%s)", sampleIdentifier,
                 externalData);
     }
 
     public void registerDataSet(String sessionToken, ExperimentIdentifier experimentIdentifier,
             NewExternalData externalData) throws UserFailureException
     {
-        logTracking(sessionToken, "register_data_set", "EXPERIMENT(%s) DATA_SET(%s)",
+        logTracking(sessionToken, "registerDataSet", "EXPERIMENT(%s) DATA_SET(%s)",
                 experimentIdentifier, externalData);
     }
 
     public Experiment tryToGetExperiment(String sessionToken,
             ExperimentIdentifier experimentIdentifier) throws UserFailureException
     {
-        logAccess(sessionToken, "get_experiment", "EXPERIMENT(%s)", experimentIdentifier);
+        logAccess(sessionToken, "tryToGetExperiment", "EXPERIMENT(%s)", experimentIdentifier);
         return null;
     }
 
     public List<Sample> listSamples(String sessionToken, ListSampleCriteria criteria)
     {
-        logAccess(sessionToken, "list_samples", "CRITERIA(%s)", criteria);
+        logAccess(sessionToken, "listSamples", "CRITERIA(%s)", criteria);
         return null;
     }
 
     public Sample tryGetSampleWithExperiment(String sessionToken, SampleIdentifier sampleIdentifier)
             throws UserFailureException
     {
-        logAccess(sessionToken, "get_sample_with_experiment", "SAMPLE(%s)", sampleIdentifier);
+        logAccess(sessionToken, "tryGetSampleWithExperiment", "SAMPLE(%s)", sampleIdentifier);
         return null;
     }
 
     public SampleType getSampleType(String sessionToken, String sampleTypeCode)
             throws UserFailureException
     {
-        logAccess(sessionToken, "get_sample_type", "SAMPLE_TYPE(%s)", sampleTypeCode);
+        logAccess(sessionToken, "getSampleType", "SAMPLE_TYPE(%s)", sampleTypeCode);
         return null;
     }
 
     public DataSetTypeWithVocabularyTerms getDataSetType(String sessionToken, String dataSetTypeCode)
     {
-        logAccess(sessionToken, "get_data_set_type", "DATA_SET_TYPE(%s)", dataSetTypeCode);
+        logAccess(sessionToken, "getDataSetType", "DATA_SET_TYPE(%s)", dataSetTypeCode);
         return null;
     }
 
     public List<ExternalData> listDataSetsBySampleID(final String sessionToken,
             final TechId sampleId, final boolean showOnlyDirectlyConnected)
     {
-        logAccess(sessionToken, "list_data_sets_by_sample_id", "SAMPLE_ID(%s)", sampleId);
+        logAccess(sessionToken, "listDataSetsBySampleID", "SAMPLE_ID(%s)", sampleId);
         return null;
     }
 
     public IEntityProperty[] tryToGetPropertiesOfTopSampleRegisteredFor(String sessionToken,
             SampleIdentifier sampleIdentifier) throws UserFailureException
     {
-        logAccess(sessionToken, "get_properties_of_top_sample", "SAMPLE(%s)", sampleIdentifier);
+        logAccess(sessionToken, "tryToGetPropertiesOfTopSampleRegisteredFor", "SAMPLE(%s)",
+                sampleIdentifier);
         return null;
     }
 
+    public void checkDataSetAccess(String sessionToken, String dataSetCode)
+            throws UserFailureException
+    {
+        logAccess(sessionToken, "checkDataSetAccess", "DATA_SET(%s)", dataSetCode);
+    }
+
     public ExternalData tryGetDataSet(String sessionToken, String dataSetCode)
             throws UserFailureException
     {
-        logAccess(sessionToken, "try_get_data_set", "DATA_SET(%s)", dataSetCode);
+        logAccess(sessionToken, "tryGetDataSet", "DATA_SET(%s)", dataSetCode);
         return null;
     }
-
+    
     public List<Sample> listSamplesByCriteria(String sessionToken,
             ListSamplesByPropertyCriteria criteria) throws UserFailureException
     {
@@ -174,7 +181,7 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLServic
     public SamplePE getSampleWithProperty(String sessionToken, String propertyTypeCode,
             GroupIdentifier groupIdentifier, String propertyValue)
     {
-        logAccess(sessionToken, "get_sample_with_property",
+        logAccess(sessionToken, "getSampleWithProperty",
                 "PROPERTY_TYPE(%s) GROUP(%s) PROPERTY_VALUE(%s)", propertyTypeCode,
                 groupIdentifier, propertyValue);
         return null;
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 8731a062eaf..34959e61638 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
@@ -25,6 +25,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.ISessionProvider;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.AuthorizationGuard;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RoleSet;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RolesAllowed;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.DataSetCodePredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.GroupIdentifierPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSampleCriteriaPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSamplesByPropertyPredicate;
@@ -115,7 +116,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleSet.ETL_SERVER)
     public DataSetTypeWithVocabularyTerms getDataSetType(String sessionToken, String dataSetTypeCode)
             throws UserFailureException;
-    
+
     /**
      * For given sample {@link TechId} returns the corresponding list of {@link ExternalData}.
      * 
@@ -201,12 +202,23 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             @AuthorizationGuard(guardClass = GroupIdentifierPredicate.class) final ExperimentIdentifier experimentIdentifier,
             final NewExternalData externalData) throws UserFailureException;
 
+    /**
+     * Does nothing besides checking that the current user has rights to access the content of the
+     * dataset.
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.OBSERVER)
+    public void checkDataSetAccess(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
+            throws UserFailureException;
+
     /**
      * Tries to return the data set specified by its code.
      */
     @Transactional
     @RolesAllowed(RoleSet.OBSERVER)
-    public ExternalData tryGetDataSet(String sessionToken, String dataSetCode)
+    public ExternalData tryGetDataSet(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
             throws UserFailureException;
 
     /**
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
index 8731a062eaf..34959e61638 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/shared/IETLLIMSService.java.expected
@@ -25,6 +25,7 @@ import ch.systemsx.cisd.openbis.generic.shared.authorization.ISessionProvider;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.AuthorizationGuard;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RoleSet;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.annotation.RolesAllowed;
+import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.DataSetCodePredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.GroupIdentifierPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSampleCriteriaPredicate;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.predicate.ListSamplesByPropertyPredicate;
@@ -115,7 +116,7 @@ public interface IETLLIMSService extends IServer, ISessionProvider
     @RolesAllowed(RoleSet.ETL_SERVER)
     public DataSetTypeWithVocabularyTerms getDataSetType(String sessionToken, String dataSetTypeCode)
             throws UserFailureException;
-    
+
     /**
      * For given sample {@link TechId} returns the corresponding list of {@link ExternalData}.
      * 
@@ -201,12 +202,23 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             @AuthorizationGuard(guardClass = GroupIdentifierPredicate.class) final ExperimentIdentifier experimentIdentifier,
             final NewExternalData externalData) throws UserFailureException;
 
+    /**
+     * Does nothing besides checking that the current user has rights to access the content of the
+     * dataset.
+     */
+    @Transactional
+    @RolesAllowed(RoleSet.OBSERVER)
+    public void checkDataSetAccess(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
+            throws UserFailureException;
+
     /**
      * Tries to return the data set specified by its code.
      */
     @Transactional
     @RolesAllowed(RoleSet.OBSERVER)
-    public ExternalData tryGetDataSet(String sessionToken, String dataSetCode)
+    public ExternalData tryGetDataSet(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodePredicate.class) String dataSetCode)
             throws UserFailureException;
 
     /**
-- 
GitLab