diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ExperimentFolderResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ExperimentFolderResolver.java
index 0e1082dce6afd80775b6fc37fc05ab3c1f5132f9..309b993dfcb98c2e7a43fca499f5993b52c9f579 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ExperimentFolderResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ExperimentFolderResolver.java
@@ -27,6 +27,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.IFtpPathResolver;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
 
@@ -76,13 +77,16 @@ public class ExperimentFolderResolver implements IFtpPathResolver
         ExperimentIdentifier identifier =
                 new ExperimentIdentifierFactory(expIdentifier).createIdentifier();
 
-        Experiment exp = service.tryToGetExperiment(sessionToken, identifier);
-        if (exp == null)
+        List<Experiment> experiments =
+                service.listExperiments(sessionToken, Collections.singletonList(identifier),
+                        new ExperimentFetchOptions());
+        if (experiments == null || experiments.isEmpty())
         {
             return Collections.emptyList();
         } else
         {
-            return childLister.listExperimentChildrenPaths(exp, expIdentifier, context);
+            return childLister.listExperimentChildrenPaths(experiments.get(0), expIdentifier,
+                    context);
         }
     }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ProjectFolderResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ProjectFolderResolver.java
index 89e5903264c5a952443c8b04f3d6429e432c294e..b3215a57a93aad6e81fa2e4ac6ee740956538eed 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ProjectFolderResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/ProjectFolderResolver.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.commons.lang.StringUtils;
@@ -27,6 +28,7 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.IFtpPathResolver;
 import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifierFactory;
 
@@ -80,6 +82,7 @@ public class ProjectFolderResolver implements IFtpPathResolver
         String sessionToken = context.getSessionToken();
         ProjectIdentifier identifier =
                 new ProjectIdentifierFactory(projectIdentifier).createIdentifier();
-        return service.listExperiments(sessionToken, identifier);
+        return service.listExperimentsForProjects(sessionToken,
+                Collections.singletonList(identifier), new ExperimentFetchOptions());
     }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java
index 692e4fdb44a8cafa50814d641e04733a87f74d7f..321e810a5b0ec5b606e302f279c2323e1891d8a2 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java
@@ -54,6 +54,7 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 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.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
 
@@ -408,16 +409,25 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver,
         ExperimentIdentifier experimentIdentifier =
                 new ExperimentIdentifierFactory(experimentId).createIdentifier();
 
-        Experiment result = null;
+        List<Experiment> result = null;
         try
         {
-            result = service.tryToGetExperiment(sessionToken, experimentIdentifier);
+            result =
+                    service.listExperiments(sessionToken,
+                            Collections.singletonList(experimentIdentifier),
+                            new ExperimentFetchOptions());
         } catch (Throwable t)
         {
             operationLog.warn("Failed to get experiment with identifier :" + experimentId, t);
         }
 
-        return result;
+        if (result == null || result.isEmpty())
+        {
+            return null;
+        } else
+        {
+            return result.get(0);
+        }
     }
 
     private String extractExperimentIdFromPath(String path)
@@ -467,8 +477,8 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver,
 
                 FtpFile childFtpFile =
                         FtpFileFactory.createFtpFile(dataSetCode, childPath,
-                                evalElement.contentNode, getContentProvider.asContent(evalElement.dataSet),
-                                fileFilter);
+                                evalElement.contentNode,
+                                getContentProvider.asContent(evalElement.dataSet), fileFilter);
                 result.add(childFtpFile);
             }
         }
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java
index dd0c5c39882cd6e893bbab5c7ef877ba0dc0dd88..1cf0010b2b02ba496f589f414ef6f93f035a5d43 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java
@@ -23,6 +23,7 @@ import java.io.InputStream;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.EnumSet;
 import java.util.List;
@@ -58,13 +59,14 @@ 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.IDatasetLocation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataSetBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
 
 /**
  * @author Kaloyan Enimanev
  */
-@Friend(toClasses=TemplateBasedDataSetResourceResolver.class)
+@Friend(toClasses = TemplateBasedDataSetResourceResolver.class)
 public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystemTestCase
 {
     private static final class SimpleFileContentProvider implements IHierarchicalContentProvider
@@ -75,7 +77,7 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         {
             this.root = root;
         }
-        
+
         public IHierarchicalContent asContent(ExternalData dataSet)
         {
             return asContent((IDatasetLocation) dataSet.tryGetAsDataSet());
@@ -98,9 +100,9 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
                     datasetDirectory, IDelegatedAction.DO_NOTHING);
         }
     }
-    
+
     private static final Date REGISTRATION_DATE = new Date(42);
-    
+
     private static final String RENDERED_REGISTRATION_DATE = TemplateBasedDataSetResourceResolver
             .extractDateValue(REGISTRATION_DATE);
 
@@ -123,14 +125,15 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
 
     private static final String BIG_TEMPLATE =
             "DS-${dataSetType}-${dataSetCode}-${dataSetDate}-${disambiguation}";
-    
 
     private TrackingMockery context;
+
     private IETLLIMSService service;
 
     private IHierarchicalContentProvider hierarchicalContentProvider;
 
     private FtpPathResolverContext resolverContext;
+
     private TemplateBasedDataSetResourceResolver resolver;
 
     private Experiment experiment;
@@ -161,19 +164,21 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         File root = new File(workingDirectory, "data-sets");
         root.mkdirs();
         simpleFileContentProvider = new SimpleFileContentProvider(root);
-        
 
-        resolverContext = new FtpPathResolverContext(SESSION_TOKEN, service, generalInfoService, null);
+        resolverContext =
+                new FtpPathResolverContext(SESSION_TOKEN, service, generalInfoService, null);
         context.checking(new Expectations()
             {
                 {
                     ExperimentIdentifier experimentIdentifier =
                             new ExperimentIdentifierFactory(EXP_ID).createIdentifier();
-                    allowing(service).tryToGetExperiment(SESSION_TOKEN, experimentIdentifier);
+                    allowing(service).listExperiments(SESSION_TOKEN,
+                            Collections.singletonList(experimentIdentifier),
+                            new ExperimentFetchOptions());
                     will(returnValue(experiment));
                 }
             });
-        
+
         ds1 =
                 new DataSetBuilder().experiment(experiment).code("ds1").type(DS_TYPE1)
                         .registrationDate(REGISTRATION_DATE).getDataSet();
@@ -208,71 +213,75 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
             throw new Error(m.getName() + "() : ", t);
         }
     }
-    
+
     @Test
     public void testInvalidConfig()
     {
         FtpServerConfig config =
-                new FtpServerConfigBuilder().withTemplate(TEMPLATE_WITH_FILENAMES).showParentsAndChildren().getConfig();
+                new FtpServerConfigBuilder().withTemplate(TEMPLATE_WITH_FILENAMES)
+                        .showParentsAndChildren().getConfig();
         try
         {
             new TemplateBasedDataSetResourceResolver(config);
             fail("ConfigurationFailureException expected");
         } catch (ConfigurationFailureException ex)
         {
-            assertEquals("Template contains file name variable and the flag " +
-            		"to show parents/children data sets is set.", ex.getMessage());
+            assertEquals("Template contains file name variable and the flag "
+                    + "to show parents/children data sets is set.", ex.getMessage());
         }
     }
-    
+
     @Test
     public void testWithParentsTopLevel()
     {
         FtpServerConfig config =
-                new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren().getConfig();
+                new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren()
+                        .getConfig();
         resolver = new TemplateBasedDataSetResourceResolver(config);
         resolver.setContentProvider(simpleFileContentProvider);
-        
-        ds1.setParents(Arrays.<ExternalData>asList(ds2));
-        final List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1);
-        
+
+        ds1.setParents(Arrays.<ExternalData> asList(ds2));
+        final List<ExternalData> dataSets = Arrays.<ExternalData> asList(ds1);
+
         prepareExperimentListExpectations(dataSets);
         prepareGetDataSetMetaData(ds1);
         prepareListDataSetsByCode(ds2);
-        
+
         String dataSetPathElement = "DS-DS_TYPE1-ds1-" + RENDERED_REGISTRATION_DATE + "-A";
         String path = EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetPathElement;
         FtpFile ftpFile = resolver.resolve(path, resolverContext);
-        
+
         assertEquals(dataSetPathElement, ftpFile.getName());
         assertEquals(path, ftpFile.getAbsolutePath());
         assertEquals(true, ftpFile.isDirectory());
         List<FtpFile> files = ftpFile.listFiles();
-        assertEquals("PARENT-DS-DS_TYPE2-ds2-" + RENDERED_REGISTRATION_DATE + "-A", files.get(0).getName());
+        assertEquals("PARENT-DS-DS_TYPE2-ds2-" + RENDERED_REGISTRATION_DATE + "-A", files.get(0)
+                .getName());
         assertEquals(true, files.get(0).isDirectory());
         assertEquals("original", files.get(1).getName());
         assertEquals(true, files.get(1).isDirectory());
         assertEquals(2, files.size());
     }
-    
+
     @Test
     public void testChildOfParent()
     {
         FtpServerConfig config =
-                new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren().getConfig();
+                new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren()
+                        .getConfig();
         resolver = new TemplateBasedDataSetResourceResolver(config);
         resolver.setContentProvider(simpleFileContentProvider);
-        
-        ds1.setParents(Arrays.<ExternalData>asList(ds2));
+
+        ds1.setParents(Arrays.<ExternalData> asList(ds2));
         ds2.setChildren(Arrays.<ExternalData> asList(ds1, ds3));
-        final List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1);
-        
+        final List<ExternalData> dataSets = Arrays.<ExternalData> asList(ds1);
+
         prepareExperimentListExpectations(dataSets);
         prepareGetDataSetMetaData(ds1);
         prepareListDataSetsByCode(ds2);
         prepareGetDataSetMetaData(ds2);
         prepareListDataSetsByCode(ds1, ds3);
-        
+
         String dataSetPathElement = "DS-DS_TYPE1-ds1-" + RENDERED_REGISTRATION_DATE + "-A";
         String ds2AsParent = "PARENT-DS-DS_TYPE2-ds2-" + RENDERED_REGISTRATION_DATE + "-A";
 
@@ -280,7 +289,7 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
                 EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetPathElement
                         + FtpConstants.FILE_SEPARATOR + ds2AsParent;
         FtpFile ftpFile = resolver.resolve(path, resolverContext);
-        
+
         assertEquals(ds2AsParent, ftpFile.getName());
         assertEquals(path, ftpFile.getAbsolutePath());
         assertEquals(true, ftpFile.isDirectory());
@@ -292,7 +301,7 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         assertEquals(true, files.get(1).isDirectory());
         assertEquals(2, files.size());
     }
-    
+
     @Test
     public void testAvoidInfiniteParentChildChains()
     {
@@ -377,51 +386,52 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
                 EXP_ID + FtpConstants.FILE_SEPARATOR + ds1.getCode() + FtpConstants.FILE_SEPARATOR
                         + subPath;
 
-        List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1);
+        List<ExternalData> dataSets = Arrays.<ExternalData> asList(ds1);
 
         prepareExperimentListExpectations(dataSets);
 
         context.checking(new Expectations()
             {
                 {
-                    IHierarchicalContent content = context.mock(IHierarchicalContent.class, ds1.getCode());
+                    IHierarchicalContent content =
+                            context.mock(IHierarchicalContent.class, ds1.getCode());
 
                     one(hierarchicalContentProvider).asContent((ExternalData) ds1);
                     will(returnValue(content));
-                    
+
                     IHierarchicalContentNode rootNode =
                             context.mock(IHierarchicalContentNode.class, "root");
                     IHierarchicalContentNode fileNode =
                             context.mock(IHierarchicalContentNode.class, "file");
-                    
+
                     one(content).getRootNode();
                     will(returnValue(rootNode));
-                    
+
                     one(rootNode).getChildNodes();
                     will(returnValue(Arrays.asList(fileNode)));
-                    
+
                     one(fileNode).getName();
                     will(returnValue(subPath));
-                    
+
                     one(fileNode).getRelativePath();
                     will(returnValue(subPath));
-                    
+
                     exactly(2).of(fileNode).isDirectory();
                     will(returnValue(false));
-                    
+
                     one(fileNode).getFileLength();
                     will(returnValue(2L));
-                    
+
                     one(fileNode).getFile();
                     will(returnValue(null));
-                    
+
                     one(fileNode).getInputStream();
                     ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {});
                     will(returnValue(is));
-                    
+
                     allowing(content).getNode(subPath);
                     will(returnValue(fileNode));
-                    
+
                     atLeast(1).of(content).close();
                 }
             });
@@ -446,8 +456,7 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         resolver = new TemplateBasedDataSetResourceResolver(config);
         resolver.setContentProvider(simpleFileContentProvider);
 
-        List<ExternalData> dataSets =
-                Arrays.<ExternalData>asList(ds1, ds2);
+        List<ExternalData> dataSets = Arrays.<ExternalData> asList(ds1, ds2);
 
         prepareExperimentListExpectations(dataSets);
 
@@ -463,14 +472,12 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         assertEquals(2, files.size());
     }
 
-
     private void prepareExperimentListExpectations(final List<ExternalData> dataSets)
     {
         context.checking(new Expectations()
             {
                 {
-                    one(service).listDataSetsByExperimentID(SESSION_TOKEN,
-                            new TechId(experiment));
+                    one(service).listDataSetsByExperimentID(SESSION_TOKEN, new TechId(experiment));
                     will(returnValue(dataSets));
                 }
             });
@@ -495,15 +502,15 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
     {
         final List<String> codes = extractCodes(dataSets);
         context.checking(new Expectations()
-        {
             {
-                one(service).listDataSetsByCode(SESSION_TOKEN, codes);
-                will(returnValue(Arrays.asList(dataSets)));
-            }
-        });
-        
+                {
+                    one(service).listDataSetsByCode(SESSION_TOKEN, codes);
+                    will(returnValue(Arrays.asList(dataSets)));
+                }
+            });
+
     }
-    
+
     private List<String> extractCodes(final ExternalData... dataSets)
     {
         final List<String> codes = new ArrayList<String>();
@@ -513,7 +520,7 @@ public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystem
         }
         return codes;
     }
-    
+
     private String getHierarchicalContentMockName(String dataSetCode)
     {
         return dataSetCode;
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index e38667f95d51bc3ecbc3e87f292eaa8af1e2e0d2..4fe150eddeae782aa60d7272015ff923347aca7c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -637,6 +637,12 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         session.setBaseIndexURL(baseIndexURL);
     }
 
+    public String getBaseIndexURL(String sessionToken)
+    {
+        final Session session = getSessionManager().getSession(sessionToken);
+        return session.getBaseIndexURL();
+    }
+
     public List<GridCustomColumn> listGridCustomColumns(String sessionToken, String gridId)
     {
         Session session = getSession(sessionToken);
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 536e4538a85295121936a9a499c0b5f8b0e5d036..16c1aa7afa42166ec9c8146b1d449a201f380f24 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
@@ -50,6 +50,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IRoleAssignmentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.IDatasetLister;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister.ExperimentLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.materiallister.IMaterialLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
@@ -116,6 +117,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityCollectionForCreationOrUpdate;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOption;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
@@ -372,6 +375,92 @@ public class ETLService extends AbstractCommonServer<IETLLIMSService> implements
         return daoFactory.getCodeSequenceDAO().getNextCodeSequenceId();
     }
 
+    public List<Experiment> listExperiments(String sessionToken,
+            List<ExperimentIdentifier> experimentIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions) throws UserFailureException
+    {
+        if (sessionToken == null)
+        {
+            throw new IllegalArgumentException("SessionToken was null");
+        }
+        if (experimentIdentifiers == null)
+        {
+            throw new IllegalArgumentException("ExperimentIdentifiers were null");
+        }
+        if (experimentFetchOptions == null)
+        {
+            throw new IllegalArgumentException("ExperimentFetchOptions were null");
+        }
+
+        if (experimentFetchOptions.containsOnlyOption(ExperimentFetchOption.BASIC))
+        {
+            ExperimentLister lister =
+                    new ExperimentLister(daoFactory, getSession(sessionToken).getBaseIndexURL());
+            return lister.listExperiments(experimentIdentifiers, experimentFetchOptions);
+        } else
+        {
+            List<Experiment> experiments = new ArrayList<Experiment>();
+            for (ExperimentIdentifier experimentIdentifier : experimentIdentifiers)
+            {
+                Experiment experiment = tryToGetExperiment(sessionToken, experimentIdentifier);
+                if (experiment != null)
+                {
+                    experiment.setFetchOptions(new ExperimentFetchOptions(ExperimentFetchOption
+                            .values()));
+                    experiments.add(experiment);
+                }
+            }
+            return experiments;
+        }
+    }
+
+    public List<Experiment> listExperimentsForProjects(String sessionToken,
+            List<ProjectIdentifier> projectIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions)
+    {
+        if (sessionToken == null)
+        {
+            throw new IllegalArgumentException("SessionToken was null");
+        }
+        if (projectIdentifiers == null)
+        {
+            throw new IllegalArgumentException("ProjectIdentifiers were null");
+        }
+        if (experimentFetchOptions == null)
+        {
+            throw new IllegalArgumentException("ExperimentFetchOptions were null");
+        }
+
+        if (experimentFetchOptions.containsOnlyOption(ExperimentFetchOption.BASIC))
+        {
+            ExperimentLister lister =
+                    new ExperimentLister(daoFactory, getSession(sessionToken).getBaseIndexURL());
+            return lister.listExperimentsForProjects(projectIdentifiers, experimentFetchOptions);
+        } else
+        {
+            List<Experiment> experiments = new ArrayList<Experiment>();
+            for (ProjectIdentifier projectIdentifier : projectIdentifiers)
+            {
+                List<Experiment> projectExperiments =
+                        listExperiments(sessionToken, projectIdentifier);
+                if (projectExperiments != null)
+                {
+                    for (Experiment projectExperiment : projectExperiments)
+                    {
+                        if (projectExperiment != null)
+                        {
+                            projectExperiment.setFetchOptions(new ExperimentFetchOptions(
+                                    ExperimentFetchOption.values()));
+                            experiments.add(projectExperiment);
+                        }
+                    }
+
+                }
+            }
+            return experiments;
+        }
+    }
+
     public Experiment tryToGetExperiment(String sessionToken,
             ExperimentIdentifier experimentIdentifier) 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 5a660f9548ac97773c39b86c12c1f03aa88dd051..32f812f42ffc6fbd5aa82ee8a45a63e2aedb46e5 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
@@ -60,6 +60,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServerInfo;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatastoreServiceDescriptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityCollectionForCreationOrUpdate;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
@@ -478,6 +479,26 @@ public class ETLServiceLogger extends AbstractServerLogger implements IETLLIMSSe
         return null;
     }
 
+    public List<Experiment> listExperiments(String sessionToken,
+            List<ExperimentIdentifier> experimentIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions)
+    {
+        logAccess(sessionToken, "listExperiments",
+                "EXPERIMENT_IDENTIFIERS(%s), EXPERIMENT_FETCH_OPTIONS(%s)", experimentIdentifiers,
+                experimentFetchOptions);
+        return null;
+    }
+
+    public List<Experiment> listExperimentsForProjects(String sessionToken,
+            List<ProjectIdentifier> projectIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions)
+    {
+        logAccess(sessionToken, "listExperimentsForProjects",
+                "PROJECT_IDENTIFIERS(%s), EXPERIMENT_FETCH_OPTIONS(%s)", projectIdentifiers,
+                experimentFetchOptions);
+        return null;
+    }
+
     public List<Project> listProjects(String sessionToken)
     {
         logAccess(sessionToken, "listProjects");
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentLister.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6d90b32e30d7f639994be3f4541cec13e36f4b1
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentLister.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import net.lemnik.eodsql.DataIterator;
+import net.lemnik.eodsql.QueryTool;
+
+import org.apache.commons.lang.StringUtils;
+
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.DatabaseContextUtils;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.basic.PermlinkUtilities;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatabaseInstance;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExperimentType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOption;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+
+/**
+ * @author pkupczyk
+ */
+public class ExperimentLister implements IExperimentLister
+{
+
+    private IDAOFactory daoFactory;
+
+    private String baseIndexURL;
+
+    private IExperimentListingQuery query;
+
+    public ExperimentLister(IDAOFactory daoFactory, String baseIndexURL)
+    {
+        this(daoFactory, QueryTool.getQuery(DatabaseContextUtils.getConnection(daoFactory),
+                IExperimentListingQuery.class), baseIndexURL);
+    }
+
+    public ExperimentLister(IDAOFactory daoFactory, IExperimentListingQuery query,
+            String baseIndexURL)
+    {
+        if (daoFactory == null)
+        {
+            throw new IllegalArgumentException("DaoFactory was null");
+        }
+        if (baseIndexURL == null)
+        {
+            throw new IllegalArgumentException("BaseIndexURL was null");
+        }
+        if (query == null)
+        {
+            throw new IllegalArgumentException("Query was null");
+        }
+        this.daoFactory = daoFactory;
+        this.baseIndexURL = baseIndexURL;
+        this.query = query;
+    }
+
+    public List<Experiment> listExperiments(List<ExperimentIdentifier> experimentIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions)
+    {
+        if (experimentIdentifiers == null)
+        {
+            throw new IllegalArgumentException("ExperimentIdentifiers were null");
+        }
+        if (experimentFetchOptions == null)
+        {
+            throw new IllegalArgumentException("ExperimentFetchOptions were null");
+        }
+        if (!experimentFetchOptions.containsOnlyOption(ExperimentFetchOption.BASIC))
+        {
+            throw new IllegalArgumentException(
+                    "Currently only ExperimentFetchOption.BASIC is supported by this method");
+        }
+
+        ExperimentIdentifiers identifiers = new ExperimentIdentifiers(experimentIdentifiers);
+
+        DataIterator<ExperimentRecord> iterator =
+                query.listExperiments(identifiers.getDatabaseInstanceCodes(),
+                        identifiers.getSpaceCodes(), identifiers.getProjectCodes(),
+                        identifiers.getExperimentCodes());
+
+        return handleResults(iterator, identifiers);
+    }
+
+    public List<Experiment> listExperimentsForProjects(List<ProjectIdentifier> projectIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions)
+    {
+        if (projectIdentifiers == null)
+        {
+            throw new IllegalArgumentException("ProjectIdentifiers were null");
+        }
+        if (experimentFetchOptions == null)
+        {
+            throw new IllegalArgumentException("ExperimentFetchOptions were null");
+        }
+        if (!experimentFetchOptions.containsOnlyOption(ExperimentFetchOption.BASIC))
+        {
+            throw new IllegalArgumentException(
+                    "Currently only ExperimentFetchOption.BASIC is supported by this method");
+        }
+
+        ProjectIdentifiers identifiers = new ProjectIdentifiers(projectIdentifiers);
+
+        DataIterator<ExperimentRecord> iterator =
+                query.listExperimentsForProjects(identifiers.getDatabaseInstanceCodes(),
+                        identifiers.getSpaceCodes(), identifiers.getProjectCodes());
+
+        return handleResults(iterator, identifiers);
+    }
+
+    private List<Experiment> handleResults(DataIterator<ExperimentRecord> iterator,
+            ProjectIdentifiers identifiers)
+    {
+        if (iterator != null)
+        {
+            List<ExperimentRecord> results = new LinkedList<ExperimentRecord>();
+            while (iterator.hasNext())
+            {
+                results.add(iterator.next());
+            }
+
+            if (identifiers.size() > 1)
+            {
+                // We may need to filter out some of the records. When there are duplicated
+                // codes among different spaces, projects or experiments the list methods
+                // may return too many results. Therefore we have to verify the results really
+                // match the identifiers list. Because it is a very rare case we
+                // prefer to do it this way rather than make the query more complicated and
+                // slower. It may only happen when there is more than one identifier
+                // specified though.
+
+                identifiers.filter(results);
+            }
+
+            return createExperiments(results);
+
+        } else
+        {
+            return Collections.emptyList();
+        }
+    }
+
+    private List<Experiment> createExperiments(List<ExperimentRecord> records)
+    {
+        List<Experiment> result = new ArrayList<Experiment>(records.size());
+        for (ExperimentRecord record : records)
+        {
+            result.add(createExperiment(record));
+        }
+        return result;
+    }
+
+    private Experiment createExperiment(ExperimentRecord record)
+    {
+        ExperimentIdentifier experimentIdentifier =
+                new ExperimentIdentifier(createDatabaseInstanceIdentifier(record)
+                        .getDatabaseInstanceCode(), record.s_code, record.pr_code, record.e_code);
+
+        Experiment experiment = new Experiment();
+        experiment.setFetchOptions(new ExperimentFetchOptions());
+        experiment.setId(record.e_id);
+        experiment.setModificationDate(record.e_modification_timestamp);
+        experiment.setCode(record.e_code);
+        experiment.setPermId(record.e_perm_id);
+        experiment.setPermlink(PermlinkUtilities.createPermlinkURL(baseIndexURL,
+                EntityKind.EXPERIMENT, record.e_perm_id));
+        experiment.setExperimentType(createExperimentType(record));
+        experiment.setIdentifier(experimentIdentifier.toString());
+        experiment.setProject(createProject(record));
+        experiment.setRegistrator(createPerson(record));
+        experiment.setRegistrationDate(record.e_registration_timestamp);
+        experiment.setModificationDate(record.e_modification_timestamp);
+        return experiment;
+    }
+
+    private ExperimentType createExperimentType(ExperimentRecord record)
+    {
+        ExperimentType experimentType = new ExperimentType();
+        experimentType.setCode(record.et_code);
+        experimentType.setDescription(record.et_description);
+        experimentType.setDatabaseInstance(createDatabaseInstance(record));
+        return experimentType;
+    }
+
+    private DatabaseInstanceIdentifier createDatabaseInstanceIdentifier(ExperimentRecord record)
+    {
+        return new DatabaseInstanceIdentifier(record.d_is_original_source, record.d_code);
+    }
+
+    private DatabaseInstance createDatabaseInstance(ExperimentRecord record)
+    {
+        DatabaseInstance instance = new DatabaseInstance();
+        instance.setId(record.d_id);
+        instance.setCode(record.d_code);
+        instance.setUuid(record.d_uuid);
+        instance.setHomeDatabase(record.d_is_original_source);
+        instance.setIdentifier(createDatabaseInstanceIdentifier(record).toString());
+        return instance;
+    }
+
+    private Space createSpace(ExperimentRecord record)
+    {
+        SpaceIdentifier spaceIdentifier =
+                new SpaceIdentifier(createDatabaseInstanceIdentifier(record)
+                        .getDatabaseInstanceCode(), record.s_code);
+
+        Space space = new Space();
+        space.setId(record.s_id);
+        space.setCode(record.s_code);
+        space.setDescription(record.s_description);
+        space.setInstance(createDatabaseInstance(record));
+        space.setRegistrationDate(record.s_registration_timestamp);
+        space.setIdentifier(spaceIdentifier.toString());
+        return space;
+    }
+
+    private Person createPerson(ExperimentRecord record)
+    {
+        Person person = new Person();
+        person.setFirstName(record.pe_first_name);
+        person.setLastName(record.pe_last_name);
+        person.setEmail(record.pe_email);
+        person.setUserId(record.pe_user_id);
+        person.setDatabaseInstance(createDatabaseInstance(record));
+        person.setRegistrationDate(record.pe_registration_timestamp);
+        return person;
+    }
+
+    private Project createProject(ExperimentRecord record)
+    {
+        ProjectIdentifier projectIdentifier =
+                new ProjectIdentifier(createDatabaseInstanceIdentifier(record)
+                        .getDatabaseInstanceCode(), record.s_code, record.pr_code);
+
+        Project project = new Project();
+        project.setId(record.pr_id);
+        project.setModificationDate(record.pr_modification_timestamp);
+        project.setRegistrationDate(record.pr_registration_timestamp);
+        project.setCode(record.pr_code);
+        project.setDescription(record.pr_description);
+        project.setSpace(createSpace(record));
+        project.setRegistrationDate(record.pr_registration_timestamp);
+        project.setIdentifier(projectIdentifier.toString());
+        return project;
+    }
+
+    private class ProjectIdentifiers
+    {
+        private Set<ProjectIdentifier> identifiersSet;
+
+        private Set<String> databaseInstanceCodes;
+
+        private Set<String> spaceCodes;
+
+        private Set<String> projectCodes;
+
+        public ProjectIdentifiers(List<? extends ProjectIdentifier> identifiers)
+        {
+            identifiersSet = new HashSet<ProjectIdentifier>(identifiers.size());
+            databaseInstanceCodes = new HashSet<String>(identifiers.size());
+            spaceCodes = new HashSet<String>(identifiers.size());
+            projectCodes = new HashSet<String>(identifiers.size());
+
+            for (ProjectIdentifier identifier : identifiers)
+            {
+                if (StringUtils.isBlank(identifier.getDatabaseInstanceCode()))
+                {
+                    identifier.setDatabaseInstanceCode(daoFactory.getHomeDatabaseInstance()
+                            .getCode());
+                }
+                identifiersSet.add(identifier);
+                databaseInstanceCodes.add(identifier.getDatabaseInstanceCode());
+                spaceCodes.add(identifier.getSpaceCode());
+                projectCodes.add(identifier.getProjectCode());
+            }
+        }
+
+        public String[] getDatabaseInstanceCodes()
+        {
+            return databaseInstanceCodes.toArray(new String[databaseInstanceCodes.size()]);
+        }
+
+        public String[] getSpaceCodes()
+        {
+            return spaceCodes.toArray(new String[spaceCodes.size()]);
+        }
+
+        public String[] getProjectCodes()
+        {
+            return projectCodes.toArray(new String[projectCodes.size()]);
+        }
+
+        public boolean contains(ProjectIdentifier identifier)
+        {
+            return identifiersSet.contains(identifier);
+        }
+
+        public int size()
+        {
+            return identifiersSet.size();
+        }
+
+        public void filter(List<ExperimentRecord> records)
+        {
+            Iterator<ExperimentRecord> iterator = records.iterator();
+            while (iterator.hasNext())
+            {
+                ExperimentRecord record = iterator.next();
+                ProjectIdentifier identifier =
+                        new ProjectIdentifier(record.d_code, record.s_code, record.pr_code);
+                if (!contains(identifier))
+                {
+                    iterator.remove();
+                }
+            }
+        }
+    }
+
+    private class ExperimentIdentifiers extends ProjectIdentifiers
+    {
+        private Set<String> experimentCodes;
+
+        public ExperimentIdentifiers(List<ExperimentIdentifier> identifiers)
+        {
+            super(identifiers);
+
+            experimentCodes = new HashSet<String>(identifiers.size());
+
+            for (ExperimentIdentifier identifier : identifiers)
+            {
+                experimentCodes.add(identifier.getExperimentCode());
+            }
+        }
+
+        public String[] getExperimentCodes()
+        {
+            return experimentCodes.toArray(new String[experimentCodes.size()]);
+        }
+
+        @Override
+        public void filter(List<ExperimentRecord> records)
+        {
+            Iterator<ExperimentRecord> iterator = records.iterator();
+            while (iterator.hasNext())
+            {
+                ExperimentRecord record = iterator.next();
+                ExperimentIdentifier identifier =
+                        new ExperimentIdentifier(record.d_code, record.s_code, record.pr_code,
+                                record.e_code);
+                if (!contains(identifier))
+                {
+                    iterator.remove();
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentRecord.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..223481068fd5151db1a6b7f7912e41a9a1f6f22f
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentRecord.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister;
+
+import java.util.Date;
+
+/**
+ * @author pkupczyk
+ */
+public class ExperimentRecord
+{
+
+    public Long e_id;
+
+    public String e_perm_id;
+
+    public String e_code;
+
+    public Date e_registration_timestamp;
+
+    public Date e_modification_timestamp;
+
+    public Long et_id;
+
+    public String et_code;
+
+    public String et_description;
+
+    public Long d_id;
+
+    public String d_code;
+
+    public Boolean d_is_original_source;
+
+    public Date d_registration_timestamp;
+
+    public String d_uuid;
+
+    public Long s_id;
+
+    public String s_code;
+
+    public String s_description;
+
+    public Date s_registration_timestamp;
+
+    public Long pr_id;
+
+    public String pr_code;
+
+    public Date pr_registration_timestamp;
+
+    public String pr_description;
+
+    public Date pr_modification_timestamp;
+
+    public Long pe_id;
+
+    public String pe_first_name;
+
+    public String pe_last_name;
+
+    public String pe_user_id;
+
+    public String pe_email;
+
+    public Date pe_registration_timestamp;
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentLister.java
new file mode 100644
index 0000000000000000000000000000000000000000..9a1fb3cc4f70b1f642254297be0797918e5e07bd
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentLister.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister;
+
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+
+/**
+ * @author pkupczyk
+ */
+public interface IExperimentLister
+{
+
+    public List<Experiment> listExperiments(List<ExperimentIdentifier> experimentIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions);
+
+    public List<Experiment> listExperimentsForProjects(List<ProjectIdentifier> projectIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentListingQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentListingQuery.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c390a1cb54abb5ade64f1a4512ef93ea28eddba
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/IExperimentListingQuery.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister;
+
+import net.lemnik.eodsql.BaseQuery;
+import net.lemnik.eodsql.DataIterator;
+import net.lemnik.eodsql.Select;
+
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.StringArrayMapper;
+
+/**
+ * @author pkupczyk
+ */
+public interface IExperimentListingQuery extends BaseQuery
+{
+
+    public static final String LIST_EXPERIMENTS =
+            " SELECT e.id as e_id, e.perm_id as e_perm_id, e.code as e_code, e.registration_timestamp as e_registration_timestamp, e.modification_timestamp as e_modification_timestamp,"
+                    + "      et.id as et_id, et.code as et_code, et.description as et_description,"
+                    + "      d.id as d_id, d.code as d_code, d.is_original_source as d_is_original_source, d.registration_timestamp as d_registration_timestamp, d.uuid as d_uuid,"
+                    + "      s.id as s_id, s.code as s_code, s.description as s_description, s.registration_timestamp as s_registration_timestamp,"
+                    + "      pr.id as pr_id, pr.code as pr_code, pr.registration_timestamp as pr_registration_timestamp, pr.description as pr_description, pr.modification_timestamp as pr_modification_timestamp,"
+                    + "      pe.id as pe_id, pe.first_name as pe_first_name, pe.last_name as pe_last_name, pe.user_id as pe_user_id, pe.email as pe_email, pe.registration_timestamp as pe_registration_timestamp"
+                    + " FROM experiments e, experiment_types et, database_instances d, spaces s, projects pr, persons pe WHERE"
+                    + "      e.exty_id = et.id AND e.proj_id = pr.id AND"
+                    + "      pr.space_id = s.id AND s.dbin_id = d.id AND e.pers_id_registerer = pe.id";
+
+    @Select(sql = LIST_EXPERIMENTS
+            + " AND d.code = any(?{1}) AND s.code = any(?{2}) AND pr.code = any(?{3}) AND e.code = any(?{4})", parameterBindings =
+        { StringArrayMapper.class, StringArrayMapper.class, StringArrayMapper.class,
+                StringArrayMapper.class })
+    public DataIterator<ExperimentRecord> listExperiments(String[] databaseInstanceCodes,
+            String[] spaceCodes, String[] projectCodes, String[] experimentCodes);
+
+    @Select(sql = LIST_EXPERIMENTS
+            + " AND d.code = any(?{1}) AND s.code = any(?{2}) AND pr.code = any(?{3})", parameterBindings =
+        { StringArrayMapper.class, StringArrayMapper.class, StringArrayMapper.class })
+    public DataIterator<ExperimentRecord> listExperimentsForProjects(
+            String[] databaseInstanceCodes, String[] spaceCodes, String[] projectCodes);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SpaceDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SpaceDAO.java
index 7b6687ad3f7e6f8cf7818267a8573a38101f96fb..7a785a98d1f180498b67b887f5872e700d004de7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SpaceDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/dataaccess/db/SpaceDAO.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.server.dataaccess.db;
 import java.util.List;
 
 import org.apache.log4j.Logger;
+import org.hibernate.FetchMode;
 import org.hibernate.SessionFactory;
 import org.hibernate.criterion.DetachedCriteria;
 import org.hibernate.criterion.Restrictions;
@@ -46,8 +47,8 @@ final class SpaceDAO extends AbstractGenericEntityDAO<SpacePE> implements ISpace
      * This logger does not output any SQL statement. If you want to do so, you had better set an
      * appropriate debugging level for class {@link JdbcAccessor}. </p>
      */
-    private static final Logger operationLog =
-            LogFactory.getLogger(LogCategory.OPERATION, SpaceDAO.class);
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
+            SpaceDAO.class);
 
     SpaceDAO(final SessionFactory sessionFactory, final DatabaseInstancePE databaseInstance)
     {
@@ -80,7 +81,9 @@ final class SpaceDAO extends AbstractGenericEntityDAO<SpacePE> implements ISpace
 
     public final List<SpacePE> listSpaces() throws DataAccessException
     {
-        final List<SpacePE> list = cast(getHibernateTemplate().loadAll(getEntityClass()));
+        final DetachedCriteria criteria = DetachedCriteria.forClass(getEntityClass());
+        criteria.setFetchMode("registrator", FetchMode.JOIN);
+        final List<SpacePE> list = cast(getHibernateTemplate().findByCriteria(criteria));
         if (operationLog.isDebugEnabled())
         {
             operationLog.debug(String.format("%s(): %d space(s) have been found.", MethodUtils
@@ -95,6 +98,7 @@ final class SpaceDAO extends AbstractGenericEntityDAO<SpacePE> implements ISpace
         assert databaseInstance != null : "Unspecified database instance.";
 
         final DetachedCriteria criteria = DetachedCriteria.forClass(getEntityClass());
+        criteria.setFetchMode("registrator", FetchMode.JOIN);
         criteria.add(Restrictions.eq("databaseInstance", databaseInstance));
         final List<SpacePE> list = cast(getHibernateTemplate().findByCriteria(criteria));
         if (operationLog.isDebugEnabled())
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
index 98c6faf67f6beeab4e54b1ad0fd1e4bac8cf3d56..4a12bed3e0aef4fd87b8aa4292622a4941e07239 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/AbstractServerLogger.java
@@ -275,6 +275,12 @@ public abstract class AbstractServerLogger implements IServer
         logAccess(sessionToken, "set_base_url", "BASE_URL(%s)", baseURL);
     }
 
+    public String getBaseIndexURL(String sessionToken)
+    {
+        logAccess(sessionToken, "get_base_url", "");
+        return null;
+    }
+
     public void setSessionUser(String sessionToken, String userID)
     {
         logMessage(authLog, Level.INFO, sessionToken, "set_session_user", "USER(%s)", new Object[]
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 90260a91c69e045158e639e9fe0e3fb8bd5b1823..0d0aa88743064fd3e281587508a76693e314ed6e 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
@@ -78,6 +78,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetShareId;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetUpdatesDTO;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStoreServerInfo;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityCollectionForCreationOrUpdate;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ListSamplesByPropertyCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.NewProperty;
@@ -474,6 +475,28 @@ public interface IETLLIMSService extends IServer, ISessionProvider
             @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
             ProjectIdentifier projectIdentifier);
 
+    /**
+     * List experiments for a given list of experiment identifiers.
+     */
+    @Transactional(readOnly = true)
+    @RolesAllowed(
+        { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    public List<Experiment> listExperiments(String sessionToken,
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            List<ExperimentIdentifier> experimentIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions);
+
+    /**
+     * List experiments for a given list of project identifiers.
+     */
+    @Transactional(readOnly = true)
+    @RolesAllowed(
+        { RoleWithHierarchy.SPACE_OBSERVER, RoleWithHierarchy.SPACE_ETL_SERVER })
+    public List<Experiment> listExperimentsForProjects(String sessionToken,
+            @AuthorizationGuard(guardClass = SpaceIdentifierPredicate.class)
+            List<ProjectIdentifier> projectIdentifiers,
+            ExperimentFetchOptions experimentFetchOptions);
+
     /**
      * List all projects that the user can see.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
index 14c1719624eb27e13d713c8878f2c87d51f1a003..fb5b129fd61bc1f76c78410262de9c6288d590bd 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/IServer.java
@@ -62,7 +62,7 @@ public interface IServer extends ISessionProvider
      */
     @Transactional
     public SessionContextDTO tryToAuthenticate(final String user, final String password);
-    
+
     @Transactional
     public SessionContextDTO tryToAuthenticateAnonymously();
 
@@ -76,11 +76,15 @@ public interface IServer extends ISessionProvider
     @Transactional
     public void setBaseIndexURL(String sessionToken, String baseIndexURL);
 
+    @Transactional
+    public String getBaseIndexURL(String sessionToken);
+
     @Transactional
     public DisplaySettings getDefaultDisplaySettings(String sessionToken);
 
     @Transactional
-    public void saveDisplaySettings(String sessionToken, DisplaySettings displaySettings, int maxEntityVisits);
+    public void saveDisplaySettings(String sessionToken, DisplaySettings displaySettings,
+            int maxEntityVisits);
 
     /**
      * Lists grid custom columns for a given grid id.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/Experiment.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/Experiment.java
index 48eda19dfaa03e0a4d0ba8fd713c184ed697b55b..9cad5cad46933abcab143cc3478dcf6ef3898ea0 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/Experiment.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/Experiment.java
@@ -24,6 +24,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWit
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityWithDeletionInformation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdAndCodeHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IPermIdHolder;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
 
 /**
  * The <i>GWT</i> equivalent to ExperimentPE.
@@ -56,6 +57,8 @@ public class Experiment extends CodeWithRegistration<Experiment> implements
 
     private String permlink;
 
+    private ExperimentFetchOptions fetchOptions;
+
     public String getPermlink()
     {
         return permlink;
@@ -126,6 +129,16 @@ public class Experiment extends CodeWithRegistration<Experiment> implements
         this.attachments = attachments;
     }
 
+    public ExperimentFetchOptions getFetchOptions()
+    {
+        return fetchOptions;
+    }
+
+    public void setFetchOptions(ExperimentFetchOptions fetchOptions)
+    {
+        this.fetchOptions = fetchOptions;
+    }
+
     //
     // IIdentifierHolder
     //
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOption.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOption.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d530699cae0c3352801b26d6974de97f6baf032
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOption.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.dto;
+
+/**
+ * @author pkupczyk
+ */
+public enum ExperimentFetchOption
+{
+
+    BASIC, PROPERTIES, PROPERTIES_OF_PROPERTIES, SCRIPTS;
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOptions.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOptions.java
new file mode 100644
index 0000000000000000000000000000000000000000..e21ef67fffdb1c7466df56440054c75e16b05fd3
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentFetchOptions.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.shared.dto;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author pkupczyk
+ */
+public class ExperimentFetchOptions implements Serializable
+{
+
+    private static final long serialVersionUID = 1L;
+
+    private Set<ExperimentFetchOption> options = new HashSet<ExperimentFetchOption>();
+
+    public ExperimentFetchOptions(ExperimentFetchOption... options)
+    {
+        // add BASIC option by default
+        addOption(ExperimentFetchOption.BASIC);
+
+        if (options != null)
+        {
+            for (ExperimentFetchOption option : options)
+            {
+                addOption(option);
+            }
+        }
+    }
+
+    public void addOption(ExperimentFetchOption option)
+    {
+        if (option == null)
+        {
+            throw new IllegalArgumentException("Option cannot be null");
+        }
+        options.add(option);
+    }
+
+    public boolean containsOption(ExperimentFetchOption option)
+    {
+        return options.contains(option);
+    }
+
+    public boolean containsOnlyOption(ExperimentFetchOption option)
+    {
+        return containsOption(option) && options.size() == 1;
+    }
+
+    @Override
+    public String toString()
+    {
+        return options.toString();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((options == null) ? 0 : options.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ExperimentFetchOptions other = (ExperimentFetchOptions) obj;
+        if (options == null)
+        {
+            if (other.options != null)
+                return false;
+        } else if (!options.equals(other.options))
+            return false;
+        return true;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
index d515a650bbbaf5aee24fc548ac4e8917cd8392f0..ddb10120db864cafaf3fdb3da88788d9bb208db7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/ExperimentPE.java
@@ -215,7 +215,7 @@ public class ExperimentPE extends AttachmentHolderPE implements
         project.addExperiment(this);
     }
 
-    @ManyToOne(fetch = FetchType.EAGER)
+    @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumn(name = ColumnNames.STUDY_OBJECT_COLUMN, updatable = false)
     public MaterialPE getStudyObject()
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/identifier/DatabaseInstanceIdentifier.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/identifier/DatabaseInstanceIdentifier.java
index 03702372acc84af3c2b005c0e8e5f2b101bcd457..ff16b0d7eed1d421111c6c4d02edd9b3c036cc52 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/identifier/DatabaseInstanceIdentifier.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/dto/identifier/DatabaseInstanceIdentifier.java
@@ -58,6 +58,17 @@ public class DatabaseInstanceIdentifier implements Serializable
         this.databaseInstanceCode = databaseInstanceCode;
     }
 
+    public DatabaseInstanceIdentifier(boolean isOriginalSource, String databaseInstanceCode)
+    {
+        if (isOriginalSource)
+        {
+            this.databaseInstanceCode = HOME;
+        } else
+        {
+            this.databaseInstanceCode = databaseInstanceCode;
+        }
+    }
+
     /**
      * Usually you should not access database instance code directly. Look for appropriate helpers.
      * 
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceDatabaseTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceDatabaseTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc66a400a8bd7a8590e22e5404c867683bc579d2
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/ETLServiceDatabaseTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
+import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOption;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+
+/**
+ * @author pkupczyk
+ */
+@Test(groups =
+    { "db", "dataset" })
+public class ETLServiceDatabaseTest extends AbstractDAOTest
+{
+    @Autowired
+    private IETLLIMSService service;
+
+    private String sessionToken;
+
+    @BeforeClass(alwaysRun = true)
+    public void init() throws SQLException
+    {
+        sessionToken = service.tryToAuthenticate("test", "password").getSessionToken();
+    }
+
+    @Test
+    public void testListExperimentsWithBasicFetchOptions()
+    {
+        List<ExperimentIdentifier> identifiers =
+                Collections.singletonList(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP1"));
+
+        List<Experiment> result =
+                service.listExperiments(sessionToken, identifiers, new ExperimentFetchOptions());
+
+        assertEquals(1, result.size());
+        assertTrue(result.get(0).getFetchOptions().containsOnlyOption(ExperimentFetchOption.BASIC));
+    }
+
+    @Test
+    public void testListExperimentsWithAllFetchOptions()
+    {
+        List<ExperimentIdentifier> identifiers =
+                Collections.singletonList(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP1"));
+
+        List<Experiment> result =
+                service.listExperiments(sessionToken, identifiers, new ExperimentFetchOptions(
+                        ExperimentFetchOption.values()));
+
+        assertEquals(1, result.size());
+
+        for (ExperimentFetchOption option : ExperimentFetchOption.values())
+        {
+            assertTrue(result.get(0).getFetchOptions().containsOption(option));
+        }
+    }
+
+    @Test
+    public void testListExperimentsForProjectsWithBasicFetchOptions()
+    {
+        List<ProjectIdentifier> identifiers =
+                Collections.singletonList(new ProjectIdentifier("CISD", "CISD", "NOE"));
+
+        List<Experiment> result =
+                service.listExperimentsForProjects(sessionToken, identifiers,
+                        new ExperimentFetchOptions());
+
+        assertEquals(1, result.size());
+        assertTrue(result.get(0).getFetchOptions().containsOnlyOption(ExperimentFetchOption.BASIC));
+    }
+
+    @Test
+    public void testListExperimentsForProjectsWithAllFetchOptions()
+    {
+        List<ProjectIdentifier> identifiers =
+                Collections.singletonList(new ProjectIdentifier("CISD", "CISD", "NOE"));
+
+        List<Experiment> result =
+                service.listExperimentsForProjects(sessionToken, identifiers,
+                        new ExperimentFetchOptions(ExperimentFetchOption.values()));
+
+        assertEquals(1, result.size());
+
+        for (ExperimentFetchOption option : ExperimentFetchOption.values())
+        {
+            assertTrue(result.get(0).getFetchOptions().containsOption(option));
+        }
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentListerTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentListerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fcab8ea185404ce8e4276dd086ebd28d5ab41592
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/experimentlister/ExperimentListerTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2009 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo.experimentlister;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.map.HashedMap;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.EntityListingTestUtils;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
+import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
+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.ExperimentType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOption;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentFetchOptions;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
+
+/**
+ * Test cases for {@link IExperimentListingQuery}.
+ * 
+ * @author Piotr Kupczyk
+ */
+@Test(groups =
+    { "db", "dataset" })
+public class ExperimentListerTest extends AbstractDAOTest
+{
+
+    @Autowired
+    private IETLLIMSService service;
+
+    private IExperimentLister lister;
+
+    private String sessionToken;
+
+    @BeforeClass(alwaysRun = true)
+    public void init() throws SQLException
+    {
+        sessionToken = service.tryToAuthenticate("test", "password").getSessionToken();
+        lister =
+                new ExperimentLister(daoFactory, EntityListingTestUtils.createQuery(daoFactory,
+                        IExperimentListingQuery.class), service.getBaseIndexURL(sessionToken));
+    }
+
+    @Test
+    public void testListExperimentsForEmptyExperimentIdentifiersListShouldReturnEmptyList()
+    {
+        List<Experiment> result =
+                lister.listExperiments(new ArrayList<ExperimentIdentifier>(),
+                        new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(new ArrayList<ExperimentIdentifier>(), result);
+    }
+
+    @Test
+    public void testListExperimentsForExistingExperimentIdentifierShouldReturnExperiment()
+    {
+        List<ExperimentIdentifier> identifiers =
+                Collections.singletonList(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP1"));
+
+        List<Experiment> result = lister.listExperiments(identifiers, new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(identifiers, result);
+    }
+
+    @Test
+    public void testListExperimentsForExistingExperimentIdentifiersShouldReturnExperiments()
+    {
+        List<ExperimentIdentifier> identifiers = new ArrayList<ExperimentIdentifier>();
+        identifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-1"));
+        identifiers.add(new ExperimentIdentifier("CISD", "CISD", "NOE", "EXP-TEST-2"));
+
+        List<Experiment> result = lister.listExperiments(identifiers, new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(identifiers, result);
+    }
+
+    @Test
+    public void testListExperimentsForExistingAndNotExistingExperimentIdentifiersShouldReturnExistingExperiments()
+    {
+        List<ExperimentIdentifier> identifiers = new ArrayList<ExperimentIdentifier>();
+        identifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-1"));
+        identifiers.add(new ExperimentIdentifier("CISD", "CISD", "NOE", "EXP-TEST-10"));
+
+        List<Experiment> result = lister.listExperiments(identifiers, new ExperimentFetchOptions());
+
+        identifiers.remove(1);
+        assertEqualToExperimentsWithIdentifiers(identifiers, result);
+    }
+
+    @Test
+    public void testListExperimentsForProjectsForEmptyProjectIdentifiersListShouldReturnEmptyList()
+    {
+        List<Experiment> result =
+                lister.listExperimentsForProjects(new ArrayList<ProjectIdentifier>(),
+                        new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(new ArrayList<ExperimentIdentifier>(), result);
+    }
+
+    @Test
+    public void testListExperimentsForProjectsForExistingProjectShouldReturnProjectExperiments()
+    {
+        List<ProjectIdentifier> projectIdentifiers =
+                Collections.singletonList(new ProjectIdentifier("CISD", "CISD", "NEMO"));
+
+        List<ExperimentIdentifier> experimentIdentifiers = new ArrayList<ExperimentIdentifier>();
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP1"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP10"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP11"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-1"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-2"));
+
+        List<Experiment> result =
+                lister.listExperimentsForProjects(projectIdentifiers, new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(experimentIdentifiers, result);
+    }
+
+    @Test
+    public void testListExperimentsForProjectsForExistingProjectsShouldReturnProjectsExperiments()
+    {
+        List<ProjectIdentifier> projectIdentifiers = new ArrayList<ProjectIdentifier>();
+        projectIdentifiers.add(new ProjectIdentifier("CISD", "CISD", "NEMO"));
+        projectIdentifiers.add(new ProjectIdentifier("CISD", "CISD", "NOE"));
+
+        List<ExperimentIdentifier> experimentIdentifiers = new ArrayList<ExperimentIdentifier>();
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP1"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP10"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP11"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-1"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NEMO", "EXP-TEST-2"));
+        experimentIdentifiers.add(new ExperimentIdentifier("CISD", "CISD", "NOE", "EXP-TEST-2"));
+
+        List<Experiment> result =
+                lister.listExperimentsForProjects(projectIdentifiers, new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(experimentIdentifiers, result);
+    }
+
+    @Test
+    public void testListExperimentsForProjectsForExistingAndNotExistingProjectIdentifiersShouldReturnExistingProjectsExperiments()
+    {
+        List<ProjectIdentifier> projectIdentifiers = new ArrayList<ProjectIdentifier>();
+        projectIdentifiers.add(new ProjectIdentifier("CISD", "CISD", "NOE"));
+        projectIdentifiers.add(new ProjectIdentifier("CISD", "CISD", "UNKNOWN-PROJECT"));
+
+        List<ExperimentIdentifier> experimentIdentifiers =
+                Collections.singletonList(new ExperimentIdentifier("CISD", "CISD", "NOE",
+                        "EXP-TEST-2"));
+
+        List<Experiment> result =
+                lister.listExperimentsForProjects(projectIdentifiers, new ExperimentFetchOptions());
+
+        assertEqualToExperimentsWithIdentifiers(experimentIdentifiers, result);
+    }
+
+    private void assertEqualToExperimentsWithIdentifiers(
+            List<ExperimentIdentifier> expectedExperimentsIdentifiers,
+            List<Experiment> actualExperiments)
+    {
+        assertEquals(expectedExperimentsIdentifiers.size(), actualExperiments.size());
+
+        Map<Long, Experiment> expectedExperimentsMap = new HashedMap<Long, Experiment>();
+        for (ExperimentIdentifier expectedExperimentIdentifier : expectedExperimentsIdentifiers)
+        {
+            Experiment expectedExperiment =
+                    service.tryToGetExperiment(sessionToken, expectedExperimentIdentifier);
+            expectedExperimentsMap.put(expectedExperiment.getId(), expectedExperiment);
+        }
+
+        for (Experiment actualExperiment : actualExperiments)
+        {
+            Experiment expectedExperiment = expectedExperimentsMap.get(actualExperiment.getId());
+            assertNotNull(expectedExperiment);
+            assertEqualsToExperiment(expectedExperiment, actualExperiment);
+        }
+
+    }
+
+    private void assertEqualsToExperiment(Experiment expected, Experiment actual)
+    {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getIdentifier(), actual.getIdentifier());
+        assertEquals(expected.getCode(), actual.getCode());
+        assertEquals(expected.getPermId(), actual.getPermId());
+        assertEquals(expected.getPermlink(), actual.getPermlink());
+        assertEquals(expected.getRegistrationDate(), actual.getRegistrationDate());
+        assertEquals(expected.getModificationDate(), actual.getModificationDate());
+        assertEqualsToExperimentType(expected.getExperimentType(), actual.getExperimentType());
+        assertEqualsToProject(expected.getProject(), actual.getProject());
+        assertEqualsToPerson(expected.getRegistrator(), actual.getRegistrator());
+        assertTrue(actual.getFetchOptions().containsOnlyOption(ExperimentFetchOption.BASIC));
+    }
+
+    private void assertEqualsToExperimentType(ExperimentType expected, ExperimentType actual)
+    {
+        assertEquals(expected.getCode(), actual.getCode());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEqualsToDatabaseInstance(expected.getDatabaseInstance(), actual.getDatabaseInstance());
+    }
+
+    private void assertEqualsToProject(Project expected, Project actual)
+    {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getIdentifier(), actual.getIdentifier());
+        assertEquals(expected.getCode(), actual.getCode());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.getModificationDate(), actual.getModificationDate());
+        assertEquals(expected.getRegistrationDate(), actual.getRegistrationDate());
+        assertEqualsToSpace(expected.getSpace(), actual.getSpace());
+    }
+
+    private void assertEqualsToPerson(Person expected, Person actual)
+    {
+        assertEquals(expected.getEmail(), actual.getEmail());
+        assertEquals(expected.getFirstName(), actual.getFirstName());
+        assertEquals(expected.getLastName(), actual.getLastName());
+        assertEquals(expected.getRegistrationDate(), actual.getRegistrationDate());
+        assertEquals(expected.getUserId(), actual.getUserId());
+        assertEqualsToDatabaseInstance(expected.getDatabaseInstance(), actual.getDatabaseInstance());
+    }
+
+    private void assertEqualsToSpace(Space expected, Space actual)
+    {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getIdentifier(), actual.getIdentifier());
+        assertEquals(expected.getCode(), actual.getCode());
+        assertEquals(expected.getDescription(), actual.getDescription());
+        assertEquals(expected.getRegistrationDate(), actual.getRegistrationDate());
+        assertEqualsToDatabaseInstance(expected.getInstance(), actual.getInstance());
+    }
+
+    private void assertEqualsToDatabaseInstance(DatabaseInstance expected, DatabaseInstance actual)
+    {
+        assertEquals(expected.getId(), actual.getId());
+        assertEquals(expected.getIdentifier(), actual.getIdentifier());
+        assertEquals(expected.getCode(), actual.getCode());
+        assertEquals(expected.getUuid(), actual.getUuid());
+    }
+
+}
\ No newline at end of file