From e76484f814f72e91a61b56bcc30f1ff6793a5972 Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Wed, 2 Sep 2009 08:33:24 +0000
Subject: [PATCH] LMS-1119 dataset lister: fetch samples for datasets, finish
 the main functionality. Note: the new solution is not yet switched on.

SVN: 12364
---
 .../grid/entity/PropertyTypesFilterUtil.java  |   2 +-
 .../server/CommonBusinessObjectFactory.java   |   8 +-
 .../openbis/generic/server/CommonServer.java  |  13 +-
 .../ExperimentProjectGroupCodeRecord.java     |   4 +-
 .../entity/ISecondaryEntityListingQuery.java  |  83 ++++++
 .../common/entity/SampleReferenceRecord.java  |  40 +++
 .../bo/common/entity/SecondaryEntityDAO.java  | 207 +++++++++++++
 .../bo/datasetlister/DatasetLister.java       | 282 ++++++++++++++++--
 .../bo/datasetlister/DatasetListerDAO.java    |   7 +-
 .../datasetlister/DatasetRelationRecord.java  |  32 ++
 .../DatasetSetListingQueryFallback.java       |  10 +-
 .../DatasetSetListingQueryFullTableScan.java  |  13 +-
 .../DatasetSetListingQueryOneByOne.java       |  10 +-
 .../bo/datasetlister/IDatasetLister.java      |   7 +-
 .../IDatasetListingFullQuery.java             |  13 +-
 .../datasetlister/IDatasetListingQuery.java   |  22 +-
 .../IDatasetSetListingQuery.java              |   8 +
 .../bo/samplelister/ISampleListingQuery.java  |  45 +--
 .../bo/samplelister/SampleLister.java         |  14 +-
 .../bo/samplelister/SampleListingWorker.java  |  70 ++---
 .../bo/common/EntityListingTestUtils.java     |   9 +-
 .../SecondaryEntityListingQueryTest.java      |  84 ++++++
 .../bo/datasetlister/DatasetListerTest.java   |  58 ++++
 .../DatasetListingQueryTest.java              | 151 ++++++++++
 .../DatasetSetListingQueryTest.java           |  73 +++++
 .../samplelister/SampleListingQueryTest.java  |  48 +--
 .../SampleSetListingQueryTest.java            |   6 +-
 27 files changed, 1123 insertions(+), 196 deletions(-)
 rename openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/{samplelister => common/entity}/ExperimentProjectGroupCodeRecord.java (60%)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ISecondaryEntityListingQuery.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SampleReferenceRecord.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityDAO.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetRelationRecord.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityListingQueryTest.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerTest.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListingQueryTest.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryTest.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/entity/PropertyTypesFilterUtil.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/entity/PropertyTypesFilterUtil.java
index 824418692cd..25c1b17f56b 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/entity/PropertyTypesFilterUtil.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/entity/PropertyTypesFilterUtil.java
@@ -88,7 +88,7 @@ public class PropertyTypesFilterUtil
             for (EntityTypePropertyType<?> assignment : assignments)
             {
                 // TODO 2009-08-27, Tomasz Pylak: use entityTypes equality when sample types
-                // obtained from sample listenr will be filled with database instance as well.
+                // obtained from sample lister will be filled with database instance as well.
                 if (entityTypesCodes.contains(assignment.getEntityType().getCode()))
                 {
                     result.add(propertyType);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
index 202f6cb728b..63e94c1c02c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
@@ -57,6 +57,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.SampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.SampleTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.VocabularyBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.VocabularyTermBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.DatasetLister;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.DatasetListerDAO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.IDatasetLister;
@@ -79,11 +80,14 @@ public final class CommonBusinessObjectFactory extends AbstractBusinessObjectFac
 
     private final DatasetListerDAO datasetListerDAO;
 
+    private final SecondaryEntityDAO referencedEntityDAO;
+
     public CommonBusinessObjectFactory(IDAOFactory daoFactory, IDataStoreServiceFactory dssFactory)
     {
         super(daoFactory, dssFactory);
         this.sampleListerDAO = SampleListerDAO.create(daoFactory);
         this.datasetListerDAO = DatasetListerDAO.create(daoFactory);
+        this.referencedEntityDAO = SecondaryEntityDAO.create(daoFactory);
     }
 
     public final IAttachmentBO createAttachmentBO(final Session session)
@@ -108,12 +112,12 @@ public final class CommonBusinessObjectFactory extends AbstractBusinessObjectFac
 
     public ISampleLister createSampleLister(Session session)
     {
-        return SampleLister.create(session.getBaseIndexURL(), sampleListerDAO);
+        return SampleLister.create(session.getBaseIndexURL(), sampleListerDAO, referencedEntityDAO);
     }
 
     public IDatasetLister createDatasetLister(Session session)
     {
-        return DatasetLister.create(datasetListerDAO);
+        return DatasetLister.create(datasetListerDAO, referencedEntityDAO);
     }
 
     public final ISampleBO createSampleBO(final Session session)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
index cad40a4503d..6769e5e0640 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
@@ -471,7 +471,18 @@ public final class CommonServer extends AbstractServer<ICommonServer> implements
     }
 
     // FIXME 2009-08-30 Tomasz Pylak: use this method
-    public final List<ExternalData> listExperimentExternalDataNew(final String sessionToken,
+    public final List<ExternalData> listSampleExternalDataFast(final String sessionToken,
+            final TechId sampleId)
+    {
+        final Session session = getSessionManager().getSession(sessionToken);
+        final IDatasetLister datasetLister = businessObjectFactory.createDatasetLister(session);
+        final List<ExternalData> datasets = datasetLister.listBySampleTechId(sampleId);
+        Collections.sort(datasets);
+        return datasets;
+    }
+
+    // FIXME 2009-08-30 Tomasz Pylak: use this method
+    public final List<ExternalData> listExperimentExternalDataFast(final String sessionToken,
             final TechId experimentId)
     {
         final Session session = getSessionManager().getSession(sessionToken);
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ExperimentProjectGroupCodeRecord.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ExperimentProjectGroupCodeRecord.java
similarity index 60%
rename from openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ExperimentProjectGroupCodeRecord.java
rename to openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ExperimentProjectGroupCodeRecord.java
index 5d627d907f1..ea02796b567 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ExperimentProjectGroupCodeRecord.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ExperimentProjectGroupCodeRecord.java
@@ -1,9 +1,9 @@
-package ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister;
+package ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity;
 
 import ch.rinn.restrictions.Private;
 
 /**
- * A r representing an experiment, project and group code.
+ * A class representing an experiment, project and group code.
  */
 @Private
 public class ExperimentProjectGroupCodeRecord
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ISecondaryEntityListingQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ISecondaryEntityListingQuery.java
new file mode 100644
index 00000000000..536d4734980
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/ISecondaryEntityListingQuery.java
@@ -0,0 +1,83 @@
+/*
+ * 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.common.entity;
+
+import it.unimi.dsi.fastutil.longs.LongSet;
+import net.lemnik.eodsql.DataIterator;
+import net.lemnik.eodsql.Select;
+import net.lemnik.eodsql.TransactionQuery;
+
+import ch.rinn.restrictions.Friend;
+import ch.rinn.restrictions.Private;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.LongSetMapper;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+
+/**
+ * Interfaces to query basic information about samples and experiments referenced from other
+ * objects. Note that this interface is not intended to be used to fetch primary entities.
+ * 
+ * @author Tomasz Pylak
+ */
+@Private
+@Friend(toClasses =
+    { SampleReferenceRecord.class })
+public interface ISecondaryEntityListingQuery extends TransactionQuery
+{
+    public static final int FETCH_SIZE = 1000;
+
+    //
+    // Experiments
+    //
+
+    /**
+     * Returns the code of an experiment and its project by the experiment <var>id</var>.
+     * 
+     * @param experimentId The id of the experiment to get the code for.
+     */
+    @Select("select e.code as e_code, et.code as et_code, p.code as p_code, g.code as g_code from experiments e "
+            + "join experiment_types et on e.exty_id=et.id join projects p on e.proj_id=p.id "
+            + "join groups g on p.grou_id=g.id where e.id=?{1}")
+    public ExperimentProjectGroupCodeRecord getExperimentAndProjectAndGroupCodeForId(
+            long experimentId);
+
+    //
+    // Samples
+    //
+    /**
+     * Returns the samples for the given ids.
+     */
+    @Select(sql = "select s.id as id, s.perm_id as perm_id, s.code as s_code, s.inva_id as inva_id, "
+            + "           st.code as st_code, g.code as g_code"
+            + "   from samples s join sample_types st on s.saty_id=st.id"
+            + "                  join groups g on s.grou_id=g.id "
+            + "        where s.id = any(?{1})", parameterBindings =
+        { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public DataIterator<SampleReferenceRecord> getSamples(LongSet sampleIds);
+
+    //
+    // Persons
+    //
+
+    /**
+     * Returns the person for the given <var>personId</var>
+     * 
+     * @param personId The id of the Person you want to get.
+     */
+    @Select("select first_name as firstName, last_name as lastName, email, user_id as userId from persons where id=?{1}")
+    public Person getPersonById(long personId);
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SampleReferenceRecord.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SampleReferenceRecord.java
new file mode 100644
index 00000000000..68bf0862104
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SampleReferenceRecord.java
@@ -0,0 +1,40 @@
+/*
+ * 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.common.entity;
+
+import ch.rinn.restrictions.Private;
+
+/**
+ * Basic information about a referenced sample.
+ * 
+ * @author Tomasz Pylak
+ */
+@Private
+public class SampleReferenceRecord
+{
+    public long id;
+
+    public String perm_id;
+
+    public String s_code;
+
+    public Long inva_id;
+
+    public String st_code;
+
+    public String g_code;
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityDAO.java
new file mode 100644
index 00000000000..3cad9fb5a31
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityDAO.java
@@ -0,0 +1,207 @@
+/*
+ * 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.common.entity;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import javax.sql.DataSource;
+
+import net.lemnik.eodsql.DataIterator;
+import net.lemnik.eodsql.QueryTool;
+
+import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.dbmigration.DatabaseConfigurationContext;
+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.server.dataaccess.PersistencyResources;
+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.Group;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Invalidation;
+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.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
+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.GroupIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.translator.DatabaseInstanceTranslator;
+
+/**
+ * @author Tomasz Pylak
+ */
+@Friend(toClasses =
+    { SampleReferenceRecord.class, ExperimentProjectGroupCodeRecord.class,
+            ISecondaryEntityListingQuery.class })
+public class SecondaryEntityDAO
+{
+    /**
+     * Creates a new instance based on {@link PersistencyResources} and home
+     * {@link DatabaseInstancePE} of specified DAO factory.
+     */
+    public static SecondaryEntityDAO create(IDAOFactory daoFactory)
+    {
+        DatabaseConfigurationContext context = DatabaseContextUtils.getDatabaseContext(daoFactory);
+        DatabaseInstancePE homeDatabaseInstance = daoFactory.getHomeDatabaseInstance();
+        return new SecondaryEntityDAO(context.getDataSource(), homeDatabaseInstance);
+    }
+
+    private final ISecondaryEntityListingQuery query;
+
+    private final DatabaseInstance databaseInstance;
+
+    private SecondaryEntityDAO(final DataSource dataSource,
+            final DatabaseInstancePE databaseInstance)
+    {
+        this.query = QueryTool.getQuery(dataSource, ISecondaryEntityListingQuery.class);
+        this.databaseInstance = DatabaseInstanceTranslator.translate(databaseInstance);
+    }
+
+    public Experiment getExperiment(final long experimentId)
+    {
+        final ExperimentProjectGroupCodeRecord record =
+                query.getExperimentAndProjectAndGroupCodeForId(experimentId);
+        return createExperiment(experimentId, record);
+    }
+
+    private Experiment createExperiment(final long experimentId,
+            final ExperimentProjectGroupCodeRecord record)
+    {
+        final Group group = new Group();
+        group.setCode(escapeHtml(record.g_code));
+        group.setInstance(databaseInstance);
+
+        final Experiment experiment = new Experiment();
+        experiment.setId(experimentId);
+        experiment.setCode(escapeHtml(record.e_code));
+        experiment.setIdentifier(new ExperimentIdentifier(null, group.getCode(), record.p_code,
+                record.e_code).toString());
+        final Project project = new Project();
+        project.setCode(escapeHtml(record.p_code));
+        project.setGroup(group);
+        experiment.setProject(project);
+        final ExperimentType experimentType = new ExperimentType();
+        experimentType.setCode(escapeHtml(record.et_code));
+        experiment.setExperimentType(experimentType);
+        return experiment;
+    }
+
+    public Person getPerson(long personId)
+    {
+        Person registrator = query.getPersonById(personId);
+        registrator.setUserId(escapeHtml(registrator.getUserId()));
+        registrator.setEmail(escapeHtml(registrator.getEmail()));
+        registrator.setFirstName(escapeHtml(registrator.getFirstName()));
+        registrator.setLastName(escapeHtml(registrator.getLastName()));
+        return registrator;
+    }
+
+    // TODO 2009-09-01, Tomasz Pylak: implement a version for h2
+    public Long2ObjectMap<Sample> getSamples(LongSet sampleIds)
+    {
+        final DataIterator<SampleReferenceRecord> sampleRecords = query.getSamples(sampleIds);
+        Long2ObjectMap<Sample> result = new Long2ObjectOpenHashMap<Sample>();
+        for (SampleReferenceRecord record : sampleRecords)
+        {
+            result.put(record.id, createSample(record, databaseInstance));
+        }
+        return result;
+    }
+
+    private static Sample createSample(SampleReferenceRecord record,
+            DatabaseInstance databaseInstance)
+    {
+        Sample sample = new Sample();
+        sample.setId(record.id);
+        sample.setCode(escapeHtml(record.s_code));
+        sample.setSampleType(createSampleType(record.st_code, databaseInstance));
+        sample.setInvalidation(createInvalidation(record.inva_id));
+        sample.setGroup(tryCreateGroup(record.g_code, databaseInstance));
+        sample.setDatabaseInstance(tryGetDatabaseInstance(record.g_code, databaseInstance));
+        sample.setPermId(escapeHtml(record.perm_id));
+        sample.setIdentifier(escapeHtml(createIdentifier(sample).toString()));
+        return sample;
+    }
+
+    private static SampleIdentifier createIdentifier(Sample sample)
+    {
+        Group group = sample.getGroup();
+        if (group != null)
+        {
+            GroupIdentifier groupIdentifier =
+                    new GroupIdentifier(group.getInstance().getCode(), group.getCode());
+            return new SampleIdentifier(groupIdentifier, sample.getCode());
+        } else
+        {
+            DatabaseInstanceIdentifier instanceIdentifier =
+                    new DatabaseInstanceIdentifier(sample.getDatabaseInstance().getCode());
+            return new SampleIdentifier(instanceIdentifier, sample.getCode());
+        }
+    }
+
+    private static DatabaseInstance tryGetDatabaseInstance(String groupCodeOrNull,
+            DatabaseInstance databaseInstance)
+    {
+        if (groupCodeOrNull == null)
+        {
+            return databaseInstance;
+        } else
+        {
+            return null;
+        }
+    }
+
+    private static Group tryCreateGroup(String codeOrNull, DatabaseInstance databaseInstance)
+    {
+        if (codeOrNull == null)
+        {
+            return null;
+        } else
+        {
+            Group group = new Group();
+            group.setCode(escapeHtml(codeOrNull));
+            group.setInstance(databaseInstance);
+            return group;
+        }
+    }
+
+    private static Invalidation createInvalidation(Long invalidationIdOrNull)
+    {
+        if (invalidationIdOrNull == null)
+        {
+            return null;
+        } else
+        {
+            return new Invalidation();
+        }
+    }
+
+    private static SampleType createSampleType(String code, DatabaseInstance databaseInstance)
+    {
+        SampleType sampleType = new SampleType();
+        sampleType.setCode(escapeHtml(code));
+        sampleType.setDatabaseInstance(databaseInstance);
+        return sampleType;
+    }
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
index 19bb4ce5876..3a726ea6867 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetLister.java
@@ -16,8 +16,11 @@
 
 package ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister;
 
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
 import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -30,22 +33,25 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.common.CodeRecord;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.EntityPropertiesEnricher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.IEntityPropertiesEnricher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.IEntityPropertiesHolderResolver;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataStore;
 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.FileFormatType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Invalidation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LocatorType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 
 /**
  * @author Tomasz Pylak
  */
 @Friend(toClasses =
-    { DatasetRecord.class })
+    { DatasetRecord.class, DatasetRelationRecord.class })
 public class DatasetLister implements IDatasetLister
 {
     //
@@ -62,10 +68,12 @@ public class DatasetLister implements IDatasetLister
 
     private final IDatasetListingQuery query;
 
-    // private final IDatasetSetListingQuery setQuery;
+    private final IDatasetSetListingQuery setQuery;
 
     private final IEntityPropertiesEnricher propertiesEnricher;
 
+    private final SecondaryEntityDAO referencedEntityDAO;
+
     //
     // Working data structures
     //
@@ -81,20 +89,20 @@ public class DatasetLister implements IDatasetLister
     private final Long2ObjectMap<LocatorType> locatorTypes =
             new Long2ObjectOpenHashMap<LocatorType>();
 
-    public static DatasetLister create(DatasetListerDAO dao)
+    public static DatasetLister create(DatasetListerDAO dao, SecondaryEntityDAO referencedEntityDAO)
     {
         IDatasetListingQuery query = dao.getQuery();
         IDatasetSetListingQuery setQuery = dao.getIdSetQuery();
         EntityPropertiesEnricher propertiesEnricher =
                 new EntityPropertiesEnricher(query, dao.getPropertySetQuery());
         return new DatasetLister(dao.getDatabaseInstanceId(), dao.getDatabaseInstance(), query,
-                setQuery, propertiesEnricher);
+                setQuery, propertiesEnricher, referencedEntityDAO);
     }
 
     // For unit tests
     DatasetLister(final long databaseInstanceId, final DatabaseInstance databaseInstance,
             final IDatasetListingQuery query, final IDatasetSetListingQuery setQuery,
-            IEntityPropertiesEnricher propertiesEnricher)
+            IEntityPropertiesEnricher propertiesEnricher, SecondaryEntityDAO referencedEntityDAO)
     {
         assert databaseInstance != null;
         assert query != null;
@@ -103,15 +111,204 @@ public class DatasetLister implements IDatasetLister
         this.databaseInstanceId = databaseInstanceId;
         this.databaseInstance = databaseInstance;
         this.query = query;
-        // this.setQuery = setQuery;
+        this.setQuery = setQuery;
         this.propertiesEnricher = propertiesEnricher;
+        this.referencedEntityDAO = referencedEntityDAO;
+    }
+
+    public List<ExternalData> listBySampleTechId(TechId sampleId)
+    {
+        try
+        {
+            return enrichDatasets(query.getDatasetsForSample(sampleId.getId()));
+        } finally
+        {
+            // Commit transaction, no need to rollback even when failed as it is readonly.
+            query.commit();
+        }
     }
 
     public List<ExternalData> listByExperimentTechId(TechId experimentId)
+    {
+        try
+        {
+            return enrichDatasets(query.getDatasetsForExperiment(experimentId.getId()));
+        } finally
+        {
+            // Commit transaction, no need to rollback even when failed as it is readonly.
+            query.commit();
+        }
+    }
+
+    private List<ExternalData> enrichDatasets(DataIterator<DatasetRecord> datasets)
     {
         loadSmallConnectedTables();
-        DataIterator<DatasetRecord> datasets = query.getDatasetsForExperiment(experimentId.getId());
-        final Long2ObjectMap<ExternalData> resultMap = createDatasets(datasets);
+        List<DatasetRecord> datasetRecords = asList(datasets);
+        final Long2ObjectMap<ExternalData> datasetMap = createPrimaryDatasets(datasetRecords);
+        enrichWithProperties(datasetMap);
+        enrichWithParents(datasetMap);
+        enrichWithExperiments(datasetMap);
+        enrichWithSamples(datasetMap);
+        return asList(datasetMap);
+    }
+
+    // assumes that the connection to the sample has been already established and sample has the
+    // id set. Should be called after enrichWithParents to ensure that parents invalidation field
+    // will be properly set.
+    private void enrichWithSamples(Long2ObjectMap<ExternalData> datasetMap)
+    {
+        LongSet ids = extractSampleIds(datasetMap);
+        Long2ObjectMap<Sample> samples = referencedEntityDAO.getSamples(ids);
+        for (ExternalData dataset : datasetMap.values())
+        {
+            long sampleId = dataset.getSample().getId();
+            Sample sample = samples.get(sampleId);
+            dataset.setSample(sample);
+            enrichWithInvalidation(dataset, sample);
+        }
+    }
+
+    private void enrichWithInvalidation(ExternalData dataset, Sample sample)
+    {
+        Invalidation invalidation = sample.getInvalidation();
+        dataset.setInvalidation(invalidation);
+        ExternalData parent = dataset.getParent();
+        if (parent != null)
+        {
+            parent.setInvalidation(invalidation);
+        }
+    }
+
+    private static LongSet extractSampleIds(Long2ObjectMap<ExternalData> datasetMap)
+    {
+        LongSet ids = new LongOpenHashSet();
+        for (ExternalData dataset : datasetMap.values())
+        {
+            long sampleId = dataset.getSample().getId();
+            ids.add(sampleId);
+        }
+        return ids;
+    }
+
+    // assumes that the connection to experiment has been already established and experiment has the
+    // id set.
+    private void enrichWithExperiments(Long2ObjectMap<ExternalData> datasetMap)
+    {
+        Long2ObjectMap<Experiment> experimentMap = new Long2ObjectOpenHashMap<Experiment>();
+
+        for (ExternalData dataset : datasetMap.values())
+        {
+            long experimentId = dataset.getExperiment().getId();
+            Experiment experiment = experimentMap.get(experimentId);
+            if (experiment == null)
+            {
+                experiment = referencedEntityDAO.getExperiment(experimentId);
+                experimentMap.put(experimentId, experiment);
+            }
+            dataset.setExperiment(experiment);
+        }
+    }
+
+    /**
+     * @param datasetMap datasets for which parents have to be resolved.
+     * @param datasetCache the original information about datasets,
+     */
+    private void enrichWithParents(final Long2ObjectMap<ExternalData> datasetMap)
+    {
+        LongSet datasetIds = extractIds(datasetMap);
+        Long2ObjectMap<ExternalData> parentsMap = resolveParents(datasetIds, datasetMap);
+        for (ExternalData dataset : datasetMap.values())
+        {
+            ExternalData parent = parentsMap.get(dataset.getId());
+            dataset.setParent(parent);
+        }
+    }
+
+    private static <T> List<T> asList(Iterable<T> items)
+    {
+        List<T> result = new ArrayList<T>();
+        for (T item : items)
+        {
+            result.add(item);
+        }
+        return result;
+    }
+
+    /**
+     * Returns a map from a child id to its parent dataset for the specified dataset ids.<br>
+     * Uses datasetCache not to resolve datasets which have already been resolved.
+     */
+    private Long2ObjectMap<ExternalData> resolveParents(LongSet datasetIds,
+            Long2ObjectMap<ExternalData> datasetCache)
+    {
+        List<DatasetRelationRecord> datasetParents = asList(setQuery.getDatasetParents(datasetIds));
+        Long2ObjectMap<ExternalData> parentsMap =
+                fetchUnknownDatasetParents(datasetParents, datasetCache);
+
+        Long2ObjectMap<ExternalData> childToParentMap = new Long2ObjectOpenHashMap<ExternalData>();
+        for (DatasetRelationRecord relation : datasetParents)
+        {
+            long parentId = relation.data_id_parent;
+            ExternalData parentDataset = getCachedItem(parentId, parentsMap, datasetCache);
+            assert parentDataset != null : "inconsistent parent dataset " + parentId;
+            childToParentMap.put(relation.data_id_child, parentDataset);
+        }
+        return childToParentMap;
+    }
+
+    // takes item from the cache. First checks in the first map, but if an item is not present looks
+    // in the second map
+    private static <T> T getCachedItem(long id, Long2ObjectMap<T> map1, Long2ObjectMap<T> map2)
+    {
+        T item = map1.get(id);
+        if (item == null)
+        {
+            item = map2.get(id);
+        }
+        return item;
+    }
+
+    /**
+     * Returns a map dataset_id -> dataset for all datasets which are parents and are not contained
+     * in a cache.
+     */
+    private Long2ObjectMap<ExternalData> fetchUnknownDatasetParents(
+            Iterable<DatasetRelationRecord> datasetParents,
+            Long2ObjectMap<ExternalData> datasetCache)
+    {
+        LongSet parentIds = extractUnknownParentIds(datasetParents, datasetCache);
+        Iterable<DatasetRecord> unknownParents = setQuery.getDatasets(parentIds);
+        Long2ObjectMap<ExternalData> parentsMap = createBasicDatasets(unknownParents);
+        return parentsMap;
+    }
+
+    private static LongSet extractUnknownParentIds(Iterable<DatasetRelationRecord> datasetParents,
+            Long2ObjectMap<ExternalData> datasetCache)
+    {
+        LongSet result = new LongOpenHashSet();
+        for (DatasetRelationRecord record : datasetParents)
+        {
+            long parentId = record.data_id_parent;
+            if (datasetCache.containsKey(parentId) == false)
+            {
+                result.add(parentId);
+            }
+        }
+        return result;
+    }
+
+    private static LongSet extractIds(Long2ObjectMap<ExternalData> datasetMap)
+    {
+        LongSet result = new LongOpenHashSet();
+        for (ExternalData dataset : datasetMap.values())
+        {
+            result.add(dataset.getId());
+        }
+        return result;
+    }
+
+    private void enrichWithProperties(final Long2ObjectMap<ExternalData> resultMap)
+    {
         propertiesEnricher.enrich(resultMap.keySet(), new IEntityPropertiesHolderResolver()
             {
                 public ExternalData get(long id)
@@ -119,8 +316,6 @@ public class DatasetLister implements IDatasetLister
                     return resultMap.get(id);
                 }
             });
-
-        return asList(resultMap);
     }
 
     private static <T> List<T> asList(Long2ObjectMap<T> items)
@@ -130,47 +325,63 @@ public class DatasetLister implements IDatasetLister
         return result;
     }
 
-    private Long2ObjectMap<ExternalData> createDatasets(DataIterator<DatasetRecord> records)
+    private Long2ObjectMap<ExternalData> createPrimaryDatasets(Iterable<DatasetRecord> records)
     {
         Long2ObjectMap<ExternalData> datasets = new Long2ObjectOpenHashMap<ExternalData>();
         for (DatasetRecord record : records)
         {
-            datasets.put(record.id, createDataset(record));
+            datasets.put(record.id, createPrimaryDataset(record));
         }
         return datasets;
     }
 
-    private ExternalData createDataset(DatasetRecord record)
+    private Long2ObjectMap<ExternalData> createBasicDatasets(Iterable<DatasetRecord> records)
     {
-        ExternalData dataset = new ExternalData();
-        dataset.setCode(record.code);
+        Long2ObjectMap<ExternalData> datasets = new Long2ObjectOpenHashMap<ExternalData>();
+        for (DatasetRecord record : records)
+        {
+            datasets.put(record.id, createBasicDataset(record));
+        }
+        return datasets;
+    }
+
+    private ExternalData createPrimaryDataset(DatasetRecord record)
+    {
+        ExternalData dataset = createBasicDataset(record);
         dataset.setComplete(BooleanOrUnknown.tryToResolve(BooleanOrUnknown
                 .valueOf(record.is_complete)));
-        dataset.setDataProducerCode(record.data_producer_code);
-        dataset.setDataSetType(dataSetTypes.get(record.dsty_id));
+        dataset.setDataProducerCode(escapeHtml(record.data_producer_code));
         dataset.setDataStore(dataStores.get(record.dast_id));
         dataset.setDerived(record.is_derived);
 
         dataset.setFileFormatType(fileFormatTypes.get(record.ffty_id));
-        dataset.setId(record.id);
-        dataset.setLocation(record.location);
+        dataset.setLocation(escapeHtml(record.location));
         dataset.setLocatorType(locatorTypes.get(record.loty_id));
         dataset.setProductionDate(record.production_timestamp);
         dataset.setRegistrationDate(record.registration_timestamp);
         dataset.setDataSetProperties(new ArrayList<IEntityProperty>());
+
         Sample sample = new Sample();
-        SampleType sampleType = new SampleType();
-        sampleType.setCode("sampleType");
-        sampleType.setDatabaseInstance(databaseInstance);
-        sample.setSampleType(sampleType);
+        sample.setId(record.samp_id);
         dataset.setSample(sample);
-        // dataset.setInvalidation(record.); // from sample
+
+        Experiment experiment = new Experiment();
+        experiment.setId(record.expe_id);
+        dataset.setExperiment(experiment);
+
+        return dataset;
+    }
+
+    private ExternalData createBasicDataset(DatasetRecord record)
+    {
+        ExternalData dataset = new ExternalData();
+        dataset.setCode(escapeHtml(record.code));
+        dataset.setDataSetType(dataSetTypes.get(record.dsty_id));
+        dataset.setId(record.id);
+
+        // TODO 2009-09-02, Tomasz Pylak: set dataset permanent links
         // dataset.setPermlink(PermlinkUtilities.createPermlinkURL(baseIndexURL,
         // EntityKind.DATA_SET, record.code));
-        // dataset.setParent(record.);
-        // dataset.setExperiment(record.);
-        // dataset.setRegistrator(record.);
-        // dataset.setSample(record.);
 
         return dataset;
     }
@@ -202,31 +413,36 @@ public class DatasetLister implements IDatasetLister
         }
     }
 
+    private static void setCode(Code<?> codeHolder, CodeRecord codeRecord)
+    {
+        codeHolder.setCode(escapeHtml(codeRecord.code));
+    }
+
     private static DataStore createDataStore(CodeRecord codeRecord)
     {
         DataStore result = new DataStore();
-        result.setCode(codeRecord.code);
+        setCode(result, codeRecord);
         return result;
     }
 
     private static LocatorType createLocatorType(CodeRecord codeRecord)
     {
         LocatorType result = new LocatorType();
-        result.setCode(codeRecord.code);
+        setCode(result, codeRecord);
         return result;
     }
 
     private static FileFormatType createFileFormatType(CodeRecord codeRecord)
     {
         FileFormatType result = new FileFormatType();
-        result.setCode(codeRecord.code);
+        setCode(result, codeRecord);
         return result;
     }
 
     private DataSetType createDataSetType(CodeRecord codeRecord)
     {
         DataSetType result = new DataSetType();
-        result.setCode(codeRecord.code);
+        setCode(result, codeRecord);
         result.setDatabaseInstance(databaseInstance);
         return result;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerDAO.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerDAO.java
index edd654f9ca5..61fc469c4e9 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerDAO.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerDAO.java
@@ -47,7 +47,7 @@ import ch.systemsx.cisd.openbis.generic.shared.translator.DatabaseInstanceTransl
  * @author Bernd Rinn
  */
 @Friend(toClasses =
-    { IDatasetListingFullQuery.class, IEntityPropertyListingQuery.class })
+    { IDatasetListingFullQuery.class, IEntityPropertyListingQuery.class, DatasetRelationRecord.class })
 public final class DatasetListerDAO
 {
     /**
@@ -221,6 +221,11 @@ public final class DatasetListerDAO
                 {
                     return query.getDatasets(sampleIds);
                 }
+
+                public Iterable<DatasetRelationRecord> getDatasetParents(LongSet entityIds)
+                {
+                    return query.getDatasetParents(entityIds);
+                }
             };
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetRelationRecord.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetRelationRecord.java
new file mode 100644
index 00000000000..d6b0bb6adfd
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetRelationRecord.java
@@ -0,0 +1,32 @@
+/*
+ * 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.datasetlister;
+
+import ch.rinn.restrictions.Private;
+
+/**
+ * Stores relation between datasets.
+ * 
+ * @author Tomasz Pylak
+ */
+@Private
+public class DatasetRelationRecord
+{
+    public long data_id_parent;
+
+    public long data_id_child;
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFallback.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFallback.java
index 3d10675fd38..151ed847d2c 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFallback.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFallback.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister;
 import it.unimi.dsi.fastutil.longs.LongSet;
 
 import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.common.exceptions.NotImplementedException;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.QueryStrategyChooser;
 
 /**
@@ -27,7 +28,8 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.common.QueryStrategyC
  * 
  * @author Tomasz Pylak
  */
-@Friend(toClasses = IDatasetListingQuery.class)
+@Friend(toClasses =
+    { IDatasetListingQuery.class, DatasetRelationRecord.class })
 class DatasetSetListingQueryFallback implements IDatasetSetListingQuery
 {
     private final IDatasetSetListingQuery oneByOneDelegate;
@@ -54,4 +56,10 @@ class DatasetSetListingQueryFallback implements IDatasetSetListingQuery
             return oneByOneDelegate.getDatasets(sampleIds);
         }
     }
+
+    public Iterable<DatasetRelationRecord> getDatasetParents(LongSet entityIds)
+    {
+        // TODO 2009-09-01, Tomasz Pylak: implement me! (h2)
+        throw new NotImplementedException();
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFullTableScan.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFullTableScan.java
index b5f22b5d23c..c5336cff737 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFullTableScan.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryFullTableScan.java
@@ -24,17 +24,18 @@ import org.apache.commons.collections15.Predicate;
 import org.apache.commons.collections15.iterators.FilterIterator;
 
 import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.common.exceptions.NotImplementedException;
 
 /**
  * An implementation of {@link IDatasetSetListingQuery} which gets all all rows and then filters
- * them down by sample id. This will be a faster way of getting the datasets then getting them one by
- * one (as {@link DatasetSetListingQueryOneByOne} does) when a the requested datasets are a
+ * them down by sample id. This will be a faster way of getting the datasets then getting them one
+ * by one (as {@link DatasetSetListingQueryOneByOne} does) when a the requested datasets are a
  * considerable part of all datasets.
  * 
  * @author Tomasz Pylak
  */
 @Friend(toClasses =
-    { DatasetRecord.class, IDatasetListingQuery.class })
+    { DatasetRecord.class, DatasetRelationRecord.class, IDatasetListingQuery.class })
 class DatasetSetListingQueryFullTableScan implements IDatasetSetListingQuery
 {
     private final IDatasetListingQuery query;
@@ -62,4 +63,10 @@ class DatasetSetListingQueryFullTableScan implements IDatasetSetListingQuery
             };
     }
 
+    public Iterable<DatasetRelationRecord> getDatasetParents(LongSet entityIds)
+    {
+        // TODO 2009-09-01, Tomasz Pylak: implement me! (h2)
+        throw new NotImplementedException();
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryOneByOne.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryOneByOne.java
index c216aa9e0e1..cf5ce884dc1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryOneByOne.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryOneByOne.java
@@ -22,13 +22,15 @@ import it.unimi.dsi.fastutil.longs.LongSet;
 import java.util.Iterator;
 
 import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.common.exceptions.NotImplementedException;
 
 /**
  * An implementation of {@link IDatasetSetListingQuery} that gets the datasets one by one.
  * 
  * @author Tomasz Pylak
  */
-@Friend(toClasses = IDatasetListingQuery.class)
+@Friend(toClasses =
+    { IDatasetListingQuery.class, DatasetRelationRecord.class })
 class DatasetSetListingQueryOneByOne implements IDatasetSetListingQuery
 {
     private final IDatasetListingQuery query;
@@ -66,4 +68,10 @@ class DatasetSetListingQueryOneByOne implements IDatasetSetListingQuery
             };
     }
 
+    public Iterable<DatasetRelationRecord> getDatasetParents(LongSet entityIds)
+    {
+        // TODO 2009-09-01, Tomasz Pylak: implement me! (h2)
+        throw new NotImplementedException();
+    }
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetLister.java
index c0e55401332..afd558205fe 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetLister.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetLister.java
@@ -22,11 +22,16 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 
 /**
+ * A class for fast dataset listing.
+ * 
  * @author Tomasz Pylak
  */
 public interface IDatasetLister
 {
-
+    /** @return datasets connected to the experiment with the specified id */
     List<ExternalData> listByExperimentTechId(TechId experimentId);
 
+    /** @return datasets connected to the sample with the specified id */
+    List<ExternalData> listBySampleTechId(TechId sampleId);
+
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingFullQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingFullQuery.java
index 09a038278b6..079a48d7b06 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingFullQuery.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingFullQuery.java
@@ -38,7 +38,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.LongSetMapper;
  * @author Tomasz Pylak
  */
 @Friend(toClasses =
-    { IDatasetListingQuery.class, IDatasetSetListingQuery.class })
+    { IDatasetListingQuery.class, IDatasetSetListingQuery.class, DatasetRelationRecord.class })
 @Private
 public interface IDatasetListingFullQuery extends IDatasetListingQuery
 {
@@ -48,10 +48,19 @@ public interface IDatasetListingFullQuery extends IDatasetListingQuery
      * <p>
      * <em>Do not call directly, call via {@link IDatasetSetListingQuery}</em>
      */
-    @Select(sql = " * from data left outer join external_data on data.id = external_data.data_id where data.id = any(?{1})", parameterBindings =
+    @Select(sql = "select * from data left outer join external_data on data.id = external_data.data_id where data.id = any(?{1})", parameterBindings =
         { LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public DataIterator<DatasetRecord> getDatasets(LongSet entityIds);
 
+    /**
+     * Returns the parent datasets of the specified datasets.
+     * <p>
+     * <em>Do not call directly, call via {@link IDatasetSetListingQuery}</em>
+     */
+    @Select(sql = "select * from data_set_relationships where data_id_child = any(?{1})", parameterBindings =
+        { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public DataIterator<DatasetRelationRecord> getDatasetParents(LongSet entityIds);
+
     /**
      * Returns the total number of all datasets in the database.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingQuery.java
index f9456f45195..46819f7b687 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingQuery.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetListingQuery.java
@@ -40,11 +40,17 @@ public interface IDatasetListingQuery extends TransactionQuery, IPropertyListing
     public static final int FETCH_SIZE = 1000;
 
     /**
-     * Returns the datasets for the given <var>experimentId</var>.
+     * Returns the datasets for the given experiment id.
      */
     @Select(sql = "select * from data left outer join external_data on data.id = external_data.data_id where data.expe_id=?{1}", fetchSize = FETCH_SIZE)
     public DataIterator<DatasetRecord> getDatasetsForExperiment(long experimentId);
 
+    /**
+     * Returns the datasets for the given sample id.
+     */
+    @Select(sql = "select * from data left outer join external_data on data.id = external_data.data_id where data.samp_id=?{1}", fetchSize = FETCH_SIZE)
+    public DataIterator<DatasetRecord> getDatasetsForSample(long sampleId);
+
     /**
      * Returns the datasets for the given <var>datasetId</var>.
      */
@@ -70,14 +76,6 @@ public interface IDatasetListingQuery extends TransactionQuery, IPropertyListing
     @Select(sql = "select id, code from locator_types")
     public CodeRecord[] getLocatorTypes();
 
-    // private Experiment experiment;
-    //
-    // private Sample sample;
-    //
-    // private Set<ExternalData> parents;
-    //
-    // private Set<ExternalData> children;
-
     // ------------- Properties
 
     /**
@@ -91,7 +89,7 @@ public interface IDatasetListingQuery extends TransactionQuery, IPropertyListing
     /**
      * Returns all generic property values of all samples.
      */
-    @Select(sql = "select pr.ds_id as entity_idetpt.t.prty_id, pr.value from data_set_properties pr"
+    @Select(sql = "select pr.ds_id as entity_id, etpt.prty_id, pr.value from data_set_properties pr"
             + "      join data_set_type_property_types etpt on pr.dstpt_id=etpt.id"
             + "   where pr.value is not null", fetchSize = FETCH_SIZE)
     public DataIterator<GenericEntityPropertyRecord> getEntityPropertyGenericValues();
@@ -104,7 +102,7 @@ public interface IDatasetListingQuery extends TransactionQuery, IPropertyListing
             + "      join data_set_type_property_types etpt on pr.dstpt_id=etpt.id"
             + "      join controlled_vocabulary_terms cvte on pr.cvte_id=cvte.id"
             + "   where pr.ds_id=?{1}")
-    public DataIterator<VocabularyTermRecord> getEntityPropertyVocabularyTermValues(long sampleId);
+    public DataIterator<VocabularyTermRecord> getEntityPropertyVocabularyTermValues(long entityId);
 
     /**
      * Returns all controlled vocabulary property values of all samples.
@@ -122,7 +120,7 @@ public interface IDatasetListingQuery extends TransactionQuery, IPropertyListing
             + "      from data_set_properties pr"
             + "      join data_set_type_property_types etpt on pr.dstpt_id=etpt.id"
             + "      join materials m on pr.mate_prop_id=m.id where pr.ds_id=?{1}")
-    public DataIterator<MaterialEntityPropertyRecord> getEntityPropertyMaterialValues(long sampleId);
+    public DataIterator<MaterialEntityPropertyRecord> getEntityPropertyMaterialValues(long entityId);
 
     /**
      * Returns all material-type property values of all samples.
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetSetListingQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetSetListingQuery.java
index 9159cebe786..712c131b5ee 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetSetListingQuery.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/IDatasetSetListingQuery.java
@@ -18,6 +18,8 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister;
 
 import it.unimi.dsi.fastutil.longs.LongSet;
 
+import ch.rinn.restrictions.Friend;
+
 /**
  * A DAO query interface for obtaining sets of datasets or dataset-related entities based on a set
  * of dataset ids.
@@ -26,6 +28,8 @@ import it.unimi.dsi.fastutil.longs.LongSet;
  * 
  * @author Tomasz Pylak
  */
+@Friend(toClasses =
+    { DatasetRelationRecord.class })
 interface IDatasetSetListingQuery
 {
     /**
@@ -33,4 +37,8 @@ interface IDatasetSetListingQuery
      */
     public Iterable<DatasetRecord> getDatasets(LongSet datasetIds);
 
+    /**
+     * Returns the parent datasets of the specified datasets.
+     */
+    public Iterable<DatasetRelationRecord> getDatasetParents(LongSet entityIds);
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ISampleListingQuery.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ISampleListingQuery.java
index 0e0f531d8a5..876d675c09f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ISampleListingQuery.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/ISampleListingQuery.java
@@ -33,19 +33,20 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.common.GenericEntityP
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.IPropertyListingQuery;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.MaterialEntityPropertyRecord;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.VocabularyTermRecord;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.ExperimentProjectGroupCodeRecord;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 
 /**
  * A {@link TransactionQuery} interface for obtaining large sets of sample-related entities from the
  * database.
  * <p>
- * This interface is intended to be used only in this package. The <code>public</code> modifier
- * is needed for creating a dynamic proxy by the EOD SQL library.
+ * This interface is intended to be used only in this package. The <code>public</code> modifier is
+ * needed for creating a dynamic proxy by the EOD SQL library.
  * 
  * @author Bernd Rinn
  */
-@Friend(toClasses={ExperimentProjectGroupCodeRecord.class})
+@Friend(toClasses =
+    { ExperimentProjectGroupCodeRecord.class })
 @Private
 public interface ISampleListingQuery extends TransactionQuery, IPropertyListingQuery
 {
@@ -178,42 +179,6 @@ public interface ISampleListingQuery extends TransactionQuery, IPropertyListingQ
     public DataIterator<SampleRecord> getSharedSamplesForSampleType(long dbInstanceId,
             long sampleTypeId);
 
-    //
-    // Experiments
-    //
-
-    /**
-     * Returns the code of an experiment and its project by the experiment <var>id</var>.
-     * 
-     * @param experimentId The id of the experiment to get the code for.
-     */
-    @Select("select e.code as e_code, et.code as et_code, p.code as p_code from experiments e "
-            + "join experiment_types et on e.exty_id=et.id join projects p on e.proj_id=p.id "
-            + "where e.id=?{1}")
-    public ExperimentProjectGroupCodeRecord getExperimentAndProjectCodeForId(long experimentId);
-
-    /**
-     * Returns the code of an experiment and its project by the experiment <var>id</var>.
-     * 
-     * @param experimentId The id of the experiment to get the code for.
-     */
-    @Select("select e.code as e_code, et.code as et_code, p.code as p_code, g.code as g_code from experiments e "
-            + "join experiment_types et on e.exty_id=et.id join projects p on e.proj_id=p.id "
-            + "join groups g on p.grou_id=g.id where e.id=?{1}")
-    public ExperimentProjectGroupCodeRecord getExperimentAndProjectAndGroupCodeForId(long experimentId);
-
-    //
-    // Persons
-    //
-
-    /**
-     * Returns the person for the given <var>personId</var>
-     * 
-     * @param personId The id of the Person you want to get.
-     */
-    @Select("select first_name as firstName, last_name as lastName, email, user_id as userId from persons where id=?{1}")
-    public Person getPersonById(long personId);
-
     //
     // Types
     //
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleLister.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleLister.java
index 89b8d3c41bf..f7bcd34aebe 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleLister.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleLister.java
@@ -18,6 +18,7 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister;
 
 import java.util.List;
 
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 
@@ -32,22 +33,27 @@ public class SampleLister implements ISampleLister
 {
     private final SampleListerDAO dao;
 
+    private final SecondaryEntityDAO referencedEntityDAO;
+
     private final String baseIndexURL;
 
-    public static SampleLister create(String baseIndexURL, SampleListerDAO dao)
+    public static SampleLister create(String baseIndexURL, SampleListerDAO dao,
+            SecondaryEntityDAO referencedEntityDAO)
     {
-        return new SampleLister(baseIndexURL, dao);
+        return new SampleLister(baseIndexURL, dao, referencedEntityDAO);
     }
 
-    private SampleLister(String baseIndexURL, SampleListerDAO dao)
+    private SampleLister(String baseIndexURL, SampleListerDAO dao,
+            SecondaryEntityDAO referencedEntityDAO)
     {
         this.baseIndexURL = baseIndexURL;
         this.dao = dao;
+        this.referencedEntityDAO = referencedEntityDAO;
     }
 
     public List<Sample> list(final ListOrSearchSampleCriteria criteria)
     {
-        return SampleListingWorker.create(criteria, baseIndexURL, dao).load();
+        return SampleListingWorker.create(criteria, baseIndexURL, dao, referencedEntityDAO).load();
     }
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingWorker.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingWorker.java
index 1c289b69eb8..70e17c74264 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingWorker.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingWorker.java
@@ -36,23 +36,22 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.EntityPropertiesEnricher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.IEntityPropertiesEnricher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.IEntityPropertiesHolderResolver;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.ExperimentProjectGroupCodeRecord;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
 import ch.systemsx.cisd.openbis.generic.shared.basic.PermlinkUtilities;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 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.Group;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Invalidation;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria;
 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.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 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.GroupIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.IdentifierHelper;
 import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
@@ -109,6 +108,8 @@ final class SampleListingWorker
 
     private final IEntityPropertiesEnricher samplePropertiesEnricherOrNull;
 
+    private final SecondaryEntityDAO referencedEntityDAO;
+
     //
     // Working data structures
     //
@@ -153,14 +154,14 @@ final class SampleListingWorker
     private final Long2ObjectMap<Sample> sampleMap = new Long2ObjectOpenHashMap<Sample>();
 
     public static SampleListingWorker create(ListOrSearchSampleCriteria criteria,
-            String baseIndexURL, SampleListerDAO dao)
+            String baseIndexURL, SampleListerDAO dao, SecondaryEntityDAO referencedEntityDAO)
     {
         ISampleListingQuery query = dao.getQuery();
         ISampleSetListingQuery setQuery = dao.getIdSetQuery();
         EntityPropertiesEnricher propertiesEnricher =
                 new EntityPropertiesEnricher(query, dao.getPropertySetQuery());
         return new SampleListingWorker(criteria, baseIndexURL, dao.getDatabaseInstanceId(), dao
-                .getDatabaseInstance(), query, setQuery, propertiesEnricher);
+                .getDatabaseInstance(), query, setQuery, propertiesEnricher, referencedEntityDAO);
     }
 
     //
@@ -171,7 +172,8 @@ final class SampleListingWorker
     SampleListingWorker(final ListOrSearchSampleCriteria criteria, final String baseIndexURL,
             final long databaseInstanceId, final DatabaseInstance databaseInstance,
             final ISampleListingQuery query, final ISampleSetListingQuery setQuery,
-            IEntityPropertiesEnricher samplePropertiesEnricherOrNull)
+            IEntityPropertiesEnricher samplePropertiesEnricherOrNull,
+            SecondaryEntityDAO referencedEntityDAO)
     {
         assert criteria != null;
         assert baseIndexURL != null;
@@ -186,6 +188,7 @@ final class SampleListingWorker
         this.query = query;
         this.setQuery = setQuery;
         this.samplePropertiesEnricherOrNull = samplePropertiesEnricherOrNull;
+        this.referencedEntityDAO = referencedEntityDAO;
     }
 
     //
@@ -262,11 +265,13 @@ final class SampleListingWorker
         {
             return null;
         }
-        final long id = experimentTechId.getId();
-        final ExperimentProjectGroupCodeRecord codes =
-                query.getExperimentAndProjectAndGroupCodeForId(id);
-        final Experiment experiment = createExperiment(id, codes, null);
-        experiments.put(id, experiment);
+        return createAndSaveExperiment(experimentTechId.getId(), null);
+    }
+
+    private Experiment createAndSaveExperiment(final long experimentId, final Group groupOrNull)
+    {
+        final Experiment experiment = referencedEntityDAO.getExperiment(experimentId);
+        experiments.put(experimentId, experiment);
         return experiment;
     }
 
@@ -520,52 +525,23 @@ final class SampleListingWorker
         Experiment experiment = experiments.get(row.expe_id);
         if (experiment == null)
         {
-            final ExperimentProjectGroupCodeRecord codes =
-                    query.getExperimentAndProjectCodeForId(row.expe_id);
-            experiment = createExperiment(row.expe_id, codes, groupOrNull);
+            experiment = createAndSaveExperiment(row.expe_id, groupOrNull);
         }
         return experiment;
     }
 
-    private Experiment createExperiment(final long experimentId,
-            final ExperimentProjectGroupCodeRecord codes, final Group groupOrNull)
+    private Person getOrCreateRegistrator(SampleRecord row)
     {
-        final Group group;
-        if (groupOrNull != null)
-        {
-            group = groupOrNull;
-        } else
-        {
-            group = new Group();
-            group.setCode(StringEscapeUtils.escapeHtml(codes.g_code));
-            group.setInstance(databaseInstance);
-        }
-        final Experiment experiment = new Experiment();
-        experiment.setId(experimentId);
-        experiment.setCode(StringEscapeUtils.escapeHtml(codes.e_code));
-        experiment.setIdentifier(new ExperimentIdentifier(null, group.getCode(), codes.p_code,
-                codes.e_code).toString());
-        final Project project = new Project();
-        project.setCode(StringEscapeUtils.escapeHtml(codes.p_code));
-        project.setGroup(group);
-        experiment.setProject(project);
-        final ExperimentType experimentType = new ExperimentType();
-        experimentType.setCode(StringEscapeUtils.escapeHtml(codes.et_code));
-        experiment.setExperimentType(experimentType);
-        return experiment;
+        return getOrCreateRegistrator(row.pers_id_registerer);
     }
 
-    private Person getOrCreateRegistrator(SampleRecord row)
+    private Person getOrCreateRegistrator(long personId)
     {
-        Person registrator = persons.get(row.pers_id_registerer);
+        Person registrator = persons.get(personId);
         if (registrator == null)
         {
-            registrator = query.getPersonById(row.pers_id_registerer);
-            registrator.setUserId(StringEscapeUtils.escapeHtml(registrator.getUserId()));
-            registrator.setEmail(StringEscapeUtils.escapeHtml(registrator.getEmail()));
-            registrator.setFirstName(StringEscapeUtils.escapeHtml(registrator.getFirstName()));
-            registrator.setLastName(StringEscapeUtils.escapeHtml(registrator.getLastName()));
-            persons.put(row.pers_id_registerer, registrator);
+            registrator = referencedEntityDAO.getPerson(personId);
+            persons.put(personId, registrator);
         }
         return registrator;
     }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/EntityListingTestUtils.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/EntityListingTestUtils.java
index 43935ffed76..b0c47ab0a9f 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/EntityListingTestUtils.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/EntityListingTestUtils.java
@@ -17,6 +17,8 @@
 package ch.systemsx.cisd.openbis.generic.server.business.bo.common;
 
 import static org.testng.AssertJUnit.fail;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -28,7 +30,7 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 /**
  * @author Tomasz Pylak
  */
-@Friend(toClasses=CodeRecord.class)
+@Friend(toClasses = CodeRecord.class)
 public class EntityListingTestUtils
 {
     public static PropertyType findPropertyType(PropertyType[] propertyTypes,
@@ -81,4 +83,9 @@ public class EntityListingTestUtils
         return null; // for compiler
     }
 
+    public static LongSet createSet(long... values)
+    {
+        return new LongOpenHashSet(values);
+    }
+
 }
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityListingQueryTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityListingQueryTest.java
new file mode 100644
index 00000000000..a13a07314ad
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/common/entity/SecondaryEntityListingQueryTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.common.entity;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.rinn.restrictions.Friend;
+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.basic.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
+
+/**
+ * @author Tomasz Pylak
+ */
+@Friend(toClasses =
+    { ExperimentProjectGroupCodeRecord.class, ISecondaryEntityListingQuery.class })
+@Test(groups =
+    { "db", "misc" })
+public class SecondaryEntityListingQueryTest extends AbstractDAOTest
+{
+
+    private ExperimentPE firstExperiment;
+
+    private PersonPE firstPerson;
+
+    private SecondaryEntityDAO dao;
+
+    @BeforeClass(alwaysRun = true)
+    public void init()
+    {
+        firstExperiment = daoFactory.getExperimentDAO().listExperiments().get(0);
+        firstPerson = daoFactory.getPersonDAO().getPerson(1);
+        dao = SecondaryEntityDAO.create(daoFactory);
+    }
+
+    @Test
+    public void testGetExperiment()
+    {
+        Experiment expFull = dao.getExperiment(firstExperiment.getId());
+        assertEquals(firstExperiment.getCode(), expFull.getCode());
+        ProjectPE project = firstExperiment.getProject();
+        assertEquals(project.getCode(), expFull.getProject().getCode());
+        assertEquals(project.getGroup().getCode(), expFull.getProject().getGroup().getCode());
+        assertEquals(firstExperiment.getEntityType().getCode(), expFull.getEntityType().getCode());
+    }
+
+    @Test
+    public void testPerson()
+    {
+        Person person = dao.getPerson(firstPerson.getId());
+        assertEquals(firstPerson.getFirstName(), person.getFirstName());
+    }
+
+    @Test
+    public void testSamples()
+    {
+        Long2ObjectMap<Sample> samples = dao.getSamples(EntityListingTestUtils.createSet(1, 2));
+        assertTrue(samples.size() > 0);
+    }
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerTest.java
new file mode 100644
index 00000000000..1bb17af55bb
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListerTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.datasetlister;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.SecondaryEntityDAO;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+
+/**
+ * @author Tomasz Pylak
+ */
+@Friend(toClasses =
+    { DatasetRecord.class, DatasetRelationRecord.class })
+@Test(groups =
+    { "db", "dataset" })
+// TODO 2009-09-01, Tomasz Pylak: replace test stubs.
+public class DatasetListerTest extends AbstractDAOTest
+{
+    private ExperimentPE firstExperiment;
+
+    private DatasetLister lister;
+
+    @BeforeClass(alwaysRun = true)
+    public void init()
+    {
+        DatasetListerDAO dao = DatasetListerDAO.create(daoFactory);
+        SecondaryEntityDAO referencedEntityDAO = SecondaryEntityDAO.create(daoFactory);
+        lister = DatasetLister.create(dao, referencedEntityDAO);
+        firstExperiment = daoFactory.getExperimentDAO().listExperiments().get(0);
+    }
+
+    @Test
+    public void testDataset()
+    {
+        // NOTE: test stub
+        lister.listByExperimentTechId(new TechId(firstExperiment.getId()));
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListingQueryTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListingQueryTest.java
new file mode 100644
index 00000000000..f6f49149582
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetListingQueryTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.datasetlister;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.ExperimentProjectGroupCodeRecord;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleListingQuery;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+
+/**
+ * Test cases for {@link ISampleListingQuery}.
+ * 
+ * @author Tomasz Pylak
+ */
+@Friend(toClasses =
+    { DatasetRecord.class, ExperimentProjectGroupCodeRecord.class, IDatasetListingQuery.class })
+@Test(groups =
+    { "db", "dataset" })
+// TODO 2009-09-01, Tomasz Pylak: replace test stubs. Now they test only that the sql is
+// gramaticaly correct, but the answer is not checked anyhow.
+public class DatasetListingQueryTest extends AbstractDAOTest
+{
+
+    private long dbInstanceId;
+
+    private ExperimentPE firstExperiment;
+
+    private IDatasetListingQuery query;
+
+    private long datasetId;
+
+    @BeforeClass(alwaysRun = true)
+    public void init()
+    {
+        DatasetListerDAO dao = DatasetListerDAO.create(daoFactory);
+        dbInstanceId = dao.getDatabaseInstanceId();
+        firstExperiment = daoFactory.getExperimentDAO().listExperiments().get(0);
+        // TODO 2009-09-01, Tomasz Pylak: get the real dataset id
+        datasetId = 1;
+        query = dao.getQuery();
+    }
+
+    @Test
+    public void testDataset()
+    {
+        // NOTE: test stub
+        query.getDataset(datasetId);
+    }
+
+    @Test
+    public void testDatasets()
+    {
+        // NOTE: test stub
+        query.getDatasets();
+    }
+
+    @Test
+    public void testDatasetsForExperiment()
+    {
+        // NOTE: test stub
+        query.getDatasetsForExperiment(firstExperiment.getId());
+    }
+
+    @Test
+    public void testDatasetTypes()
+    {
+        // NOTE: test stub
+        query.getDatasetTypes(dbInstanceId);
+    }
+
+    @Test
+    public void testDataStores()
+    {
+        // NOTE: test stub
+        query.getDataStores(dbInstanceId);
+    }
+
+    @Test
+    public void testLocatorTypes()
+    {
+        // NOTE: test stub
+        query.getLocatorTypes();
+    }
+
+    @Test
+    public void testEntityPropertyGenericValues()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyGenericValues();
+    }
+
+    @Test
+    public void testEntityPropertyGenericValuesForDataset()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyGenericValues(datasetId);
+    }
+
+    @Test
+    public void testEntityPropertyMaterialValues()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyMaterialValues();
+    }
+
+    @Test
+    public void testEntityPropertyMaterialValuesForDataset()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyMaterialValues(datasetId);
+    }
+
+    @Test
+    public void testEntityPropertyVocabularyTermValues()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyVocabularyTermValues();
+    }
+
+    @Test
+    public void testEntityPropertyVocabularyTermValuesForDataset()
+    {
+        // NOTE: test stub
+        query.getEntityPropertyVocabularyTermValues(datasetId);
+    }
+
+    @Test
+    public void testFileFormatTypes()
+    {
+        // NOTE: test stub
+        query.getFileFormatTypes(dbInstanceId);
+    }
+}
\ No newline at end of file
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryTest.java
new file mode 100644
index 00000000000..0736a084e28
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/datasetlister/DatasetSetListingQueryTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.datasetlister;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import ch.rinn.restrictions.Friend;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
+
+/**
+ * Test cases for {@link IDatasetSetListingQuery}.
+ * 
+ * @author Tomasz Pylak
+ */
+@Friend(toClasses =
+    { DatasetRecord.class, IDatasetListingQuery.class })
+@Test(groups =
+    { "db", "dataset" })
+public class DatasetSetListingQueryTest extends AbstractDAOTest
+{
+
+    private IDatasetSetListingQuery query;
+
+    @BeforeClass(alwaysRun = true)
+    public void init()
+    {
+        DatasetListerDAO dao = DatasetListerDAO.create(daoFactory);
+        query = dao.getIdSetQuery();
+    }
+
+    @Test
+    public void testDatasets()
+    {
+        // NOTE: test stub
+        LongSet ids = getDatasetIds();
+        query.getDatasets(ids);
+    }
+
+    @Test
+    public void testDatasetParents()
+    {
+        // NOTE: test stub
+        LongSet ids = getDatasetIds();
+        query.getDatasetParents(ids);
+    }
+
+    private LongSet getDatasetIds()
+    {
+        // NOTE: get the real dataset ids!
+        LongSet ids = new LongOpenHashSet();
+        ids.add(1);
+        ids.add(2);
+        return ids;
+    }
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingQueryTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingQueryTest.java
index 5981eed01e7..9ad7bb12137 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingQueryTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleListingQueryTest.java
@@ -44,18 +44,16 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.common.CodeRecord;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.GenericEntityPropertyRecord;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.MaterialEntityPropertyRecord;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.common.VocabularyTermRecord;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.common.entity.ExperimentProjectGroupCodeRecord;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.AbstractDAOTest;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IIdHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Person;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatabaseInstancePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.GroupPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.PersonPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
@@ -65,7 +63,8 @@ import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
  * 
  * @author Bernd Rinn
  */
-@Friend(toClasses={SampleRecord.class, ExperimentProjectGroupCodeRecord.class, ISampleListingQuery.class})
+@Friend(toClasses =
+    { SampleRecord.class, ExperimentProjectGroupCodeRecord.class, ISampleListingQuery.class })
 @Test(groups =
     { "db", "sample" })
 public class SampleListingQueryTest extends AbstractDAOTest
@@ -95,10 +94,6 @@ public class SampleListingQueryTest extends AbstractDAOTest
 
     private SamplePE firstMasterPlate;
 
-    private ExperimentPE firstExperiment;
-
-    private PersonPE firstPerson;
-
     private ISampleListingQuery query;
 
     @BeforeClass(alwaysRun = true)
@@ -116,8 +111,6 @@ public class SampleListingQueryTest extends AbstractDAOTest
         firstMasterPlate =
                 daoFactory.getSampleDAO().listSamplesWithPropertiesByTypeAndDatabaseInstance(
                         masterPlateType, dbInstance).get(0);
-        firstExperiment = daoFactory.getExperimentDAO().listExperiments().get(0);
-        firstPerson = daoFactory.getPersonDAO().getPerson(1);
         query = sampleListerDAO.getQuery();
     }
 
@@ -237,7 +230,6 @@ public class SampleListingQueryTest extends AbstractDAOTest
         assertNull(sample2.samp_id_part_of);
     }
 
-
     private Long getSampleTypeId(String sampleTypeCode)
     {
         return daoFactory.getSampleTypeDAO().tryFindSampleTypeByCode(sampleTypeCode).getId();
@@ -341,31 +333,6 @@ public class SampleListingQueryTest extends AbstractDAOTest
         assertEquals(SHARED_MASTER_PLATE_ID, sample.id);
     }
 
-    @Test
-    public void testGetExperiment()
-    {
-        ExperimentProjectGroupCodeRecord expFull =
-                query.getExperimentAndProjectAndGroupCodeForId(firstExperiment.getId());
-        assertEquals(firstExperiment.getCode(), expFull.e_code);
-        ProjectPE project = firstExperiment.getProject();
-        assertEquals(project.getCode(), expFull.p_code);
-        assertEquals(project.getGroup().getCode(), expFull.g_code);
-        assertEquals(firstExperiment.getEntityType().getCode(), expFull.et_code);
-
-        ExperimentProjectGroupCodeRecord expNoGroup =
-                query.getExperimentAndProjectCodeForId(firstExperiment.getId());
-        assertNull(expNoGroup.g_code);
-        expFull.g_code = null;
-        assertTrue(EqualsBuilder.reflectionEquals(expNoGroup, expFull));
-    }
-
-    @Test
-    public void testPerson()
-    {
-        Person person = query.getPersonById(firstPerson.getId());
-        assertEquals(firstPerson.getFirstName(), person.getFirstName());
-    }
-
     @Test
     public void testSampleType()
     {
@@ -407,7 +374,8 @@ public class SampleListingQueryTest extends AbstractDAOTest
     @Test
     public void testSamplePropertiesGenericValues()
     {
-        List<GenericEntityPropertyRecord> properties = asList(query.getEntityPropertyGenericValues());
+        List<GenericEntityPropertyRecord> properties =
+                asList(query.getEntityPropertyGenericValues());
         assertCorrectSampleAndPropertyTypeReferences(properties);
         for (GenericEntityPropertyRecord property : properties)
         {
@@ -426,7 +394,8 @@ public class SampleListingQueryTest extends AbstractDAOTest
         {
             assertTrue("Property type not found " + property.prty_id, propertyTypesIds
                     .contains(property.prty_id));
-            assertTrue("Sample not found " + property.entity_id, sampleIds.contains(property.entity_id));
+            assertTrue("Sample not found " + property.entity_id, sampleIds
+                    .contains(property.entity_id));
         }
     }
 
@@ -466,7 +435,8 @@ public class SampleListingQueryTest extends AbstractDAOTest
     @Test
     public void testSamplePropertiesMaterialValues()
     {
-        List<MaterialEntityPropertyRecord> properties = asList(query.getEntityPropertyMaterialValues());
+        List<MaterialEntityPropertyRecord> properties =
+                asList(query.getEntityPropertyMaterialValues());
         assertCorrectSampleAndPropertyTypeReferences(properties);
         for (MaterialEntityPropertyRecord property : properties)
         {
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleSetListingQueryTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleSetListingQueryTest.java
index 7719aac1421..6da5d82eb05 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleSetListingQueryTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/samplelister/SampleSetListingQueryTest.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister;
 
 import static ch.systemsx.cisd.openbis.generic.server.business.bo.common.EntityListingTestUtils.asList;
+import static ch.systemsx.cisd.openbis.generic.server.business.bo.common.EntityListingTestUtils.createSet;
 import static org.testng.AssertJUnit.assertEquals;
 import static org.testng.AssertJUnit.assertTrue;
 import static org.testng.AssertJUnit.fail;
@@ -114,11 +115,6 @@ public class SampleSetListingQueryTest extends AbstractDAOTest
         propertySetQuery = sampleListerDAO.getPropertySetQuery();
     }
 
-    private static LongSet createSet(long... values)
-    {
-        return new LongOpenHashSet(values);
-    }
-
     @Test
     public void testQuerySamples()
     {
-- 
GitLab