From a67db1ef85735b8f9a525b8e623145f126d77e95 Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Mon, 10 Aug 2015 15:06:06 +0000
Subject: [PATCH] SSDM-944 : V3 AS API - improve performance of map and search
 methods - HistoryRelation

SVN: 34434
---
 .../method/SearchMaterialMethodExecutor.java  |   3 +-
 .../SearchMaterialSqlMethodExecutor.java      |   3 +-
 .../entity/dataset/DataSetTranslator.java     |  28 ++-
 .../dataset/sql/DataSetHistoryRelation.java   | 119 +++++++++++
 .../entity/dataset/sql/DataSetQuery.java      |  48 +++++
 .../sql/DataSetRelationshipRecord.java        |  33 +++
 .../experiment/ExperimentTranslator.java      |  28 ++-
 .../sql/ExperimentHistoryRelation.java        |  94 ++++++++
 .../experiment/sql/ExperimentQuery.java       |  48 +++++
 .../sql/ExperimentRelationshipRecord.java     |  33 +++
 .../history/sql/HistoryPropertyRecord.java    |  45 ++++
 .../entity/history/sql/HistoryRelation.java   | 202 ++++++++++++++++++
 .../sql/HistoryRelationshipRecord.java        |  41 ++++
 .../material/sql/MaterialHistoryRelation.java |  62 ++++++
 .../entity/material/sql/MaterialQuery.java    |   8 +
 .../material/sql/MaterialSqlTranslator.java   |  12 ++
 .../entity/project/ProjectTranslator.java     |  29 +++
 .../project/sql/ProjectHistoryRelation.java   |  88 ++++++++
 .../entity/project/sql/ProjectQuery.java      |  40 ++++
 .../sql/ProjectRelationshipRecord.java        |  31 +++
 .../entity/sample/SampleTranslator.java       |  19 +-
 .../sample/sql/SampleHistoryRelation.java     | 119 +++++++++++
 .../entity/sample/sql/SampleQuery.java        |  48 +++++
 .../sample/sql/SampleRelationshipRecord.java  |  35 +++
 .../systemtest/api/v3/MapExperimentTest.java  |   2 +-
 .../entity/history/ProjectRelationType.java   |  30 +++
 .../api/v3/dto/entity/project/Project.java    |  26 ++-
 .../project/ProjectFetchOptions.java          |  26 +++
 .../api/v3/dto/generators/Generator.java      |   2 +
 29 files changed, 1282 insertions(+), 20 deletions(-)
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetHistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetQuery.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetRelationshipRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentHistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentQuery.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentRelationshipRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryPropertyRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelationshipRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialHistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectHistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectQuery.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectRelationshipRecord.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleHistoryRelation.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleQuery.java
 create mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleRelationshipRecord.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/ProjectRelationType.java

diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialMethodExecutor.java
index 32bf0b76905..23be89ec7da 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialMethodExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialMethodExecutor.java
@@ -17,7 +17,6 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.executor.method;
 
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.common.ISearchObjectExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ISearchMaterialExecutor;
@@ -31,7 +30,7 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.MaterialPE;
 /**
  * @author pkupczyk
  */
-@Component
+// @Component
 public class SearchMaterialMethodExecutor extends
         AbstractSearchMethodExecutor<Material, MaterialPE, MaterialSearchCriterion, MaterialFetchOptions>
         implements ISearchMaterialMethodExecutor
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialSqlMethodExecutor.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialSqlMethodExecutor.java
index 6bd47addf7b..6e2b16517ad 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialSqlMethodExecutor.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/executor/method/SearchMaterialSqlMethodExecutor.java
@@ -17,6 +17,7 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.executor.method;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.common.ISearchObjectExecutor;
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ISearchMaterialIdExecutor;
@@ -29,7 +30,7 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriter
 /**
  * @author pkupczyk
  */
-// @Component
+@Component
 public class SearchMaterialSqlMethodExecutor extends
         AbstractSearchMethodExecutor<Material, Long, MaterialSearchCriterion, MaterialFetchOptions>
         implements ISearchMaterialMethodExecutor
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/DataSetTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/DataSetTranslator.java
index fe3439e2562..1b511186167 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/DataSetTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/DataSetTranslator.java
@@ -17,8 +17,10 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -26,8 +28,8 @@ import org.springframework.stereotype.Component;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.AbstractCachingTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.Relations;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.sql.DataSetHistoryRelation;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.IExperimentTranslator;
-import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.IHistoryTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.material.IMaterialPropertyTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.person.IPersonTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.property.IPropertyTranslator;
@@ -73,9 +75,6 @@ public class DataSetTranslator extends AbstractCachingTranslator<DataPE, DataSet
     @Autowired
     private IExternalDataTranslator externalDataTranslator;
 
-    @Autowired
-    private IHistoryTranslator historyTranslator;
-
     @Override
     protected boolean shouldTranslate(TranslationContext context, DataPE input, DataSetFetchOptions fetchOptions)
     {
@@ -97,6 +96,24 @@ public class DataSetTranslator extends AbstractCachingTranslator<DataPE, DataSet
         return dataSet;
     }
 
+    @Override
+    protected Relations getObjectsRelations(TranslationContext context, Collection<DataPE> dataSets, DataSetFetchOptions fetchOptions)
+    {
+        Relations relations = new Relations();
+
+        if (fetchOptions.hasHistory())
+        {
+            Set<Long> dataSetIds = new HashSet<Long>();
+            for (DataPE dataSet : dataSets)
+            {
+                dataSetIds.add(dataSet.getId());
+            }
+            relations.add(createRelation(DataSetHistoryRelation.class, context, dataSetIds, fetchOptions.withHistory()));
+        }
+
+        return relations;
+    }
+
     @Override
     protected void updateObject(TranslationContext context, DataPE dataPe, DataSet result, Relations relations, DataSetFetchOptions fetchOptions)
     {
@@ -194,7 +211,8 @@ public class DataSetTranslator extends AbstractCachingTranslator<DataPE, DataSet
 
         if (fetchOptions.hasHistory())
         {
-            result.setHistory(historyTranslator.translate(context, dataPe, fetchOptions.withHistory()));
+            DataSetHistoryRelation relation = relations.get(DataSetHistoryRelation.class);
+            result.setHistory(relation.getRelated(dataPe.getId()));
             result.getFetchOptions().withHistoryUsing(fetchOptions.withHistory());
         }
     }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetHistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetHistoryRelation.java
new file mode 100644
index 00000000000..fd834f21cf1
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetHistoryRelation.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.sql;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.lemnik.eodsql.QueryTool;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.DataSetRelationType;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.RelationHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.person.Person;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.DataSetPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SamplePermId;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.dto.RelationType;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class DataSetHistoryRelation extends HistoryRelation
+{
+
+    @Autowired
+    private IDAOFactory daoFactory;
+
+    public DataSetHistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        super(context, entityIds, fetchOptions);
+    }
+
+    @Override
+    protected List<HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds)
+    {
+        DataSetQuery query = QueryTool.getManagedQuery(DataSetQuery.class);
+        return query.getPropertiesHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected List<? extends HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds)
+    {
+        DataSetQuery query = QueryTool.getManagedQuery(DataSetQuery.class);
+        return query.getRelationshipsHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected RelationHistoryEntry createRelationshipEntry(HistoryRelationshipRecord record, Map<Long, Person> authorMap)
+    {
+        RelationHistoryEntry entry = super.createRelationshipEntry(record, authorMap);
+
+        DataSetRelationshipRecord dataSetRecord = (DataSetRelationshipRecord) record;
+
+        if (dataSetRecord.experimentId != null)
+        {
+            entry.setRelationType(DataSetRelationType.EXPERIMENT);
+            entry.setRelatedObjectId(new ExperimentPermId(dataSetRecord.relatedObjectId));
+        } else if (dataSetRecord.sampleId != null)
+        {
+            entry.setRelationType(DataSetRelationType.SAMPLE);
+            entry.setRelatedObjectId(new SamplePermId(dataSetRecord.relatedObjectId));
+        } else if (dataSetRecord.dataSetId != null)
+        {
+            RelationType relationType = RelationType.valueOf(dataSetRecord.relationType);
+
+            switch (relationType)
+            {
+                case PARENT:
+                    entry.setRelationType(DataSetRelationType.CHILD);
+                    break;
+                case CHILD:
+                    entry.setRelationType(DataSetRelationType.PARENT);
+                    break;
+                case CONTAINER:
+                    entry.setRelationType(DataSetRelationType.CONTAINED);
+                    break;
+                case CONTAINED:
+                case COMPONENT:
+                    entry.setRelationType(DataSetRelationType.CONTAINER);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported relation type: " + relationType);
+            }
+            entry.setRelatedObjectId(new DataSetPermId(dataSetRecord.relatedObjectId));
+        }
+
+        return entry;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetQuery.java
new file mode 100644
index 00000000000..fbe8965ba78
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetQuery.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.sql;
+
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import java.util.List;
+
+import net.lemnik.eodsql.Select;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectQuery;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
+
+/**
+ * @author pkupczyk
+ */
+public interface DataSetQuery extends ObjectQuery
+{
+
+    @Select(sql = "select dph.ds_id as entityId, dph.pers_id_author as authorId, pt.code as propertyCode, dph.value as propertyValue, dph.material as materialPropertyValue, dph.vocabulary_term as vocabularyPropertyValue, dph.valid_from_timestamp as validFrom, dph.valid_until_timestamp as validTo "
+            + "from data_set_properties_history dph "
+            + "left join data_set_type_property_types dtpt on dph.dstpt_id = dtpt.id "
+            + "left join property_types pt on dtpt.prty_id = pt.id "
+            + "where dph.ds_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<HistoryPropertyRecord> getPropertiesHistory(LongSet dataSetIds);
+
+    @Select(sql = "select drh.main_data_id as entityId, drh.pers_id_author as authorId, drh.relation_type as relationType, "
+            + "drh.entity_perm_id as relatedObjectId, drh.valid_from_timestamp as validFrom, drh.valid_until_timestamp as validTo, "
+            + "drh.expe_id as experimentId, drh.samp_id as sampleId, drh.data_id as dataSetId "
+            + "from data_set_relationships_history drh where drh.valid_until_timestamp is not null and drh.main_data_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<DataSetRelationshipRecord> getRelationshipsHistory(LongSet dataSetIds);
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetRelationshipRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetRelationshipRecord.java
new file mode 100644
index 00000000000..12f5488c82a
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/dataset/sql/DataSetRelationshipRecord.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.sql;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class DataSetRelationshipRecord extends HistoryRelationshipRecord
+{
+
+    public Long experimentId;
+
+    public Long sampleId;
+
+    public Long dataSetId;
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/ExperimentTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/ExperimentTranslator.java
index caa0ed7a149..f28a568d467 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/ExperimentTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/ExperimentTranslator.java
@@ -17,9 +17,11 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -29,7 +31,7 @@ import ch.ethz.sis.openbis.generic.server.api.v3.translator.Relations;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.attachment.IAttachmentTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.IDataSetTranslator;
-import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.IHistoryTranslator;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.sql.ExperimentHistoryRelation;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.material.IMaterialPropertyTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.person.IPersonTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.project.IProjectTranslator;
@@ -85,9 +87,6 @@ public class ExperimentTranslator extends AbstractCachingTranslator<ExperimentPE
     @Autowired
     private ITagTranslator tagTranslator;
 
-    @Autowired
-    private IHistoryTranslator historyTranslator;
-
     @Override
     protected boolean shouldTranslate(TranslationContext context, ExperimentPE input, ExperimentFetchOptions fetchOptions)
     {
@@ -109,6 +108,24 @@ public class ExperimentTranslator extends AbstractCachingTranslator<ExperimentPE
         return result;
     }
 
+    @Override
+    protected Relations getObjectsRelations(TranslationContext context, Collection<ExperimentPE> experiments, ExperimentFetchOptions fetchOptions)
+    {
+        Relations relations = new Relations();
+
+        if (fetchOptions.hasHistory())
+        {
+            Set<Long> experimentIds = new HashSet<Long>();
+            for (ExperimentPE experiment : experiments)
+            {
+                experimentIds.add(experiment.getId());
+            }
+            relations.add(createRelation(ExperimentHistoryRelation.class, context, experimentIds, fetchOptions.withHistory()));
+        }
+
+        return relations;
+    }
+
     @Override
     protected void updateObject(TranslationContext context, ExperimentPE experiment, Experiment result, Relations relations,
             ExperimentFetchOptions fetchOptions)
@@ -179,7 +196,8 @@ public class ExperimentTranslator extends AbstractCachingTranslator<ExperimentPE
 
         if (fetchOptions.hasHistory())
         {
-            result.setHistory(historyTranslator.translate(context, experiment, fetchOptions.withHistory()));
+            ExperimentHistoryRelation relation = relations.get(ExperimentHistoryRelation.class);
+            result.setHistory(relation.getRelated(experiment.getId()));
             result.getFetchOptions().withHistoryUsing(fetchOptions.withHistory());
         }
     }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentHistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentHistoryRelation.java
new file mode 100644
index 00000000000..5b1782412b2
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentHistoryRelation.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.sql;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.lemnik.eodsql.QueryTool;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.ExperimentRelationType;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.RelationHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.person.Person;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.DataSetPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.project.ProjectPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SamplePermId;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class ExperimentHistoryRelation extends HistoryRelation
+{
+
+    public ExperimentHistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        super(context, entityIds, fetchOptions);
+    }
+
+    @Override
+    protected List<HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds)
+    {
+        ExperimentQuery query = QueryTool.getManagedQuery(ExperimentQuery.class);
+        return query.getPropertiesHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected List<? extends HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds)
+    {
+        ExperimentQuery query = QueryTool.getManagedQuery(ExperimentQuery.class);
+        return query.getRelationshipsHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected RelationHistoryEntry createRelationshipEntry(HistoryRelationshipRecord record, Map<Long, Person> authorMap)
+    {
+        RelationHistoryEntry entry = super.createRelationshipEntry(record, authorMap);
+
+        ExperimentRelationshipRecord experimentRecord = (ExperimentRelationshipRecord) record;
+
+        if (experimentRecord.projectId != null)
+        {
+            entry.setRelationType(ExperimentRelationType.PROJECT);
+            entry.setRelatedObjectId(new ProjectPermId(experimentRecord.relatedObjectId));
+        } else if (experimentRecord.sampleId != null)
+        {
+            entry.setRelationType(ExperimentRelationType.SAMPLE);
+            entry.setRelatedObjectId(new SamplePermId(experimentRecord.relatedObjectId));
+        } else if (experimentRecord.dataSetId != null)
+        {
+            entry.setRelationType(ExperimentRelationType.DATA_SET);
+            entry.setRelatedObjectId(new DataSetPermId(experimentRecord.relatedObjectId));
+        }
+
+        return entry;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentQuery.java
new file mode 100644
index 00000000000..c8c520c8dd5
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentQuery.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.sql;
+
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import java.util.List;
+
+import net.lemnik.eodsql.Select;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectQuery;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
+
+/**
+ * @author pkupczyk
+ */
+public interface ExperimentQuery extends ObjectQuery
+{
+
+    @Select(sql = "select eph.expe_id as entityId, eph.pers_id_author as authorId, pt.code as propertyCode, eph.value as propertyValue, eph.material as materialPropertyValue, eph.vocabulary_term as vocabularyPropertyValue, eph.valid_from_timestamp as validFrom, eph.valid_until_timestamp as validTo "
+            + "from experiment_properties_history eph "
+            + "left join experiment_type_property_types etpt on eph.etpt_id = etpt.id "
+            + "left join property_types pt on etpt.prty_id = pt.id "
+            + "where eph.expe_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<HistoryPropertyRecord> getPropertiesHistory(LongSet experimentIds);
+
+    @Select(sql = "select erh.main_expe_id as entityId, erh.pers_id_author as authorId, erh.relation_type as relationType, "
+            + "erh.entity_perm_id as relatedObjectId, erh.valid_from_timestamp as validFrom, erh.valid_until_timestamp as validTo, "
+            + "erh.proj_id as projectId, erh.samp_id as sampleId, erh.data_id as dataSetId "
+            + "from experiment_relationships_history erh where erh.valid_until_timestamp is not null and erh.main_expe_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<ExperimentRelationshipRecord> getRelationshipsHistory(LongSet experimentIds);
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentRelationshipRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentRelationshipRecord.java
new file mode 100644
index 00000000000..5d5f3186b50
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/experiment/sql/ExperimentRelationshipRecord.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.sql;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class ExperimentRelationshipRecord extends HistoryRelationshipRecord
+{
+
+    public Long projectId;
+
+    public Long sampleId;
+
+    public Long dataSetId;
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryPropertyRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryPropertyRecord.java
new file mode 100644
index 00000000000..2bc75d8b6f0
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryPropertyRecord.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql;
+
+import java.util.Date;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectBaseRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class HistoryPropertyRecord extends ObjectBaseRecord
+{
+
+    public Long entityId;
+
+    public Long authorId;
+
+    public String propertyCode;
+
+    public String propertyValue;
+
+    public String materialPropertyValue;
+
+    public String vocabularyPropertyValue;
+
+    public Date validFrom;
+
+    public Date validTo;
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelation.java
new file mode 100644
index 00000000000..bf3462f29c7
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelation.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.Relation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.person.sql.IPersonSqlTranslator;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.HistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.PropertyHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.RelationHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.person.Person;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+
+/**
+ * @author pkupczyk
+ */
+public abstract class HistoryRelation implements Relation
+{
+
+    @Autowired
+    private IPersonSqlTranslator personTranslator;
+
+    private TranslationContext context;
+
+    private Collection<Long> entityIds;
+
+    private HistoryEntryFetchOptions fetchOptions;
+
+    private Map<Long, List<HistoryEntry>> entriesMap = new HashMap<Long, List<HistoryEntry>>();
+
+    public HistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        this.context = context;
+        this.entityIds = entityIds;
+        this.fetchOptions = fetchOptions;
+    }
+
+    @SuppressWarnings("hiding")
+    protected abstract List<? extends HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds);
+
+    @SuppressWarnings("hiding")
+    protected abstract List<? extends HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds);
+
+    @Override
+    public void load()
+    {
+        List<? extends HistoryPropertyRecord> properties = loadPropertyHistory(entityIds);
+        List<? extends HistoryRelationshipRecord> relationships = loadRelationshipHistory(entityIds);
+
+        Map<Long, Person> authorMap = new HashMap<>();
+
+        if (fetchOptions.hasAuthor())
+        {
+            Set<Long> authorIds = new HashSet<Long>();
+            if (properties != null)
+            {
+                for (HistoryPropertyRecord property : properties)
+                {
+                    if (property.authorId != null)
+                    {
+                        authorIds.add(property.authorId);
+                    }
+                }
+            }
+            if (relationships != null)
+            {
+                for (HistoryRelationshipRecord relationship : relationships)
+                {
+                    if (relationship.authorId != null)
+                    {
+                        authorIds.add(relationship.authorId);
+                    }
+                }
+            }
+            authorMap = personTranslator.translate(context, authorIds, fetchOptions.withAuthor());
+        }
+
+        if (properties != null)
+        {
+            createPropertyEntries(properties, authorMap);
+        }
+        if (relationships != null)
+        {
+            createRelationshipEntries(relationships, authorMap);
+        }
+    }
+
+    private void createPropertyEntries(List<? extends HistoryPropertyRecord> records, Map<Long, Person> authorMap)
+    {
+        for (HistoryPropertyRecord record : records)
+        {
+            List<HistoryEntry> entries = entriesMap.get(record.entityId);
+
+            if (entries == null)
+            {
+                entries = new LinkedList<HistoryEntry>();
+                entriesMap.put(record.entityId, entries);
+            }
+
+            entries.add(createPropertyEntry(record, authorMap));
+        }
+    }
+
+    protected PropertyHistoryEntry createPropertyEntry(HistoryPropertyRecord record, Map<Long, Person> authorMap)
+    {
+        PropertyHistoryEntry entry = new PropertyHistoryEntry();
+        entry.setFetchOptions(new HistoryEntryFetchOptions());
+        entry.setValidFrom(record.validFrom);
+        entry.setValidTo(record.validTo);
+        entry.setPropertyName(record.propertyCode);
+
+        if (record.propertyValue != null)
+        {
+            entry.setPropertyValue(record.propertyValue);
+        } else if (record.vocabularyPropertyValue != null)
+        {
+            entry.setPropertyValue(record.vocabularyPropertyValue);
+        } else if (record.materialPropertyValue != null)
+        {
+            entry.setPropertyValue(record.materialPropertyValue);
+        } else
+        {
+            throw new IllegalArgumentException("Unexpected property history entry with all values null");
+        }
+
+        if (fetchOptions.hasAuthor())
+        {
+            entry.setAuthor(authorMap.get(record.authorId));
+            entry.getFetchOptions().withAuthorUsing(fetchOptions.withAuthor());
+        }
+        return entry;
+    }
+
+    private void createRelationshipEntries(List<? extends HistoryRelationshipRecord> records, Map<Long, Person> authorMap)
+    {
+        for (HistoryRelationshipRecord record : records)
+        {
+            List<HistoryEntry> entries = entriesMap.get(record.entityId);
+
+            if (entries == null)
+            {
+                entries = new LinkedList<HistoryEntry>();
+                entriesMap.put(record.entityId, entries);
+            }
+
+            entries.add(createRelationshipEntry(record, authorMap));
+        }
+    }
+
+    protected RelationHistoryEntry createRelationshipEntry(HistoryRelationshipRecord record, Map<Long, Person> authorMap)
+    {
+        RelationHistoryEntry entry = new RelationHistoryEntry();
+        entry.setFetchOptions(new HistoryEntryFetchOptions());
+        entry.setValidFrom(record.validFrom);
+        entry.setValidTo(record.validTo);
+
+        if (fetchOptions.hasAuthor())
+        {
+            entry.setAuthor(authorMap.get(record.authorId));
+            entry.getFetchOptions().withAuthorUsing(fetchOptions.withAuthor());
+        }
+
+        return entry;
+    }
+
+    public List<HistoryEntry> getRelated(Long entityId)
+    {
+        if (entriesMap.containsKey(entityId))
+        {
+            return entriesMap.get(entityId);
+        } else
+        {
+            return Collections.emptyList();
+        }
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelationshipRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelationshipRecord.java
new file mode 100644
index 00000000000..3ef9002ce42
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/history/sql/HistoryRelationshipRecord.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql;
+
+import java.util.Date;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectBaseRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class HistoryRelationshipRecord extends ObjectBaseRecord
+{
+
+    public Long entityId;
+
+    public Long authorId;
+
+    public String relationType;
+
+    public String relatedObjectId;
+
+    public Date validFrom;
+
+    public Date validTo;
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialHistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialHistoryRelation.java
new file mode 100644
index 00000000000..2d9c99e8078
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialHistoryRelation.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.material.sql;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+
+import java.util.Collection;
+import java.util.List;
+
+import net.lemnik.eodsql.QueryTool;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class MaterialHistoryRelation extends HistoryRelation
+{
+
+    public MaterialHistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        super(context, entityIds, fetchOptions);
+    }
+
+    @Override
+    protected List<HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds)
+    {
+        MaterialQuery query = QueryTool.getManagedQuery(MaterialQuery.class);
+        return query.getPropertiesHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected List<HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds)
+    {
+        return null;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialQuery.java
index 744a621dfdd..20fae081478 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialQuery.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialQuery.java
@@ -25,6 +25,7 @@ import net.lemnik.eodsql.Select;
 
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectQuery;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectRelationRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
 import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
 
 /**
@@ -54,6 +55,13 @@ public interface MaterialQuery extends ObjectQuery
             + "where mp.mate_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
     public List<MaterialPropertyRecord> getProperties(LongSet materialIds);
 
+    @Select(sql = "select mph.mate_id as entityId, mph.pers_id_author as authorId, pt.code as propertyCode, mph.value as propertyValue, mph.material as materialPropertyValue, mph.vocabulary_term as vocabularyPropertyValue, mph.valid_from_timestamp as validFrom, mph.valid_until_timestamp as validTo "
+            + "from material_properties_history mph "
+            + "left join material_type_property_types mtpt on mph.mtpt_id = mtpt.id "
+            + "left join property_types pt on mtpt.prty_id = pt.id "
+            + "where mph.mate_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<HistoryPropertyRecord> getPropertiesHistory(LongSet materialIds);
+
     @Select(sql = "select mp.mate_id as materialId, pt.code as propertyCode, mp.mate_prop_id as propertyValue "
             + "from material_properties mp "
             + "left join material_type_property_types mtpt on mp.mtpt_id = mtpt.id "
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialSqlTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialSqlTranslator.java
index 860c9c80b5d..50d0ef150ff 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialSqlTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/material/sql/MaterialSqlTranslator.java
@@ -75,6 +75,11 @@ public class MaterialSqlTranslator extends AbstractCachingTranslator<Long, Mater
             relations.add(createRelation(MaterialTagsRelation.class, context, materialIds, fetchOptions.withTags()));
         }
 
+        if (fetchOptions.hasHistory())
+        {
+            relations.add(createRelation(MaterialHistoryRelation.class, context, materialIds, fetchOptions.withHistory()));
+        }
+
         return relations;
     }
 
@@ -125,5 +130,12 @@ public class MaterialSqlTranslator extends AbstractCachingTranslator<Long, Mater
             result.getFetchOptions().withTagsUsing(fetchOptions.withTags());
         }
 
+        if (fetchOptions.hasHistory())
+        {
+            MaterialHistoryRelation relation = relations.get(MaterialHistoryRelation.class);
+            result.setHistory(relation.getRelated(materialId));
+            result.getFetchOptions().withHistoryUsing(fetchOptions.withHistory());
+        }
+
     }
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/ProjectTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/ProjectTranslator.java
index f1e2750c694..c8252d30687 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/ProjectTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/ProjectTranslator.java
@@ -17,8 +17,11 @@
 package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.project;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -29,6 +32,7 @@ import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.attachment.IAttachmentTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.IExperimentTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.person.IPersonTranslator;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.project.sql.ProjectHistoryRelation;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.space.ISpaceTranslator;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.attachment.Attachment;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.Experiment;
@@ -81,6 +85,24 @@ public class ProjectTranslator extends AbstractCachingTranslator<ProjectPE, Proj
         return result;
     }
 
+    @Override
+    protected Relations getObjectsRelations(TranslationContext context, Collection<ProjectPE> projects, ProjectFetchOptions fetchOptions)
+    {
+        Relations relations = new Relations();
+
+        if (fetchOptions.hasHistory())
+        {
+            Set<Long> projectIds = new HashSet<Long>();
+            for (ProjectPE project : projects)
+            {
+                projectIds.add(project.getId());
+            }
+            relations.add(createRelation(ProjectHistoryRelation.class, context, projectIds, fetchOptions.withHistory()));
+        }
+
+        return relations;
+    }
+
     @Override
     protected void updateObject(TranslationContext context, ProjectPE project, Project result, Relations relations, ProjectFetchOptions fetchOptions)
     {
@@ -123,5 +145,12 @@ public class ProjectTranslator extends AbstractCachingTranslator<ProjectPE, Proj
             result.getFetchOptions().withAttachmentsUsing(fetchOptions.withAttachments());
         }
 
+        if (fetchOptions.hasHistory())
+        {
+            ProjectHistoryRelation relation = relations.get(ProjectHistoryRelation.class);
+            result.setHistory(relation.getRelated(project.getId()));
+            result.getFetchOptions().withHistoryUsing(fetchOptions.withHistory());
+        }
+
     }
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectHistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectHistoryRelation.java
new file mode 100644
index 00000000000..14e91ccc54b
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectHistoryRelation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.project.sql;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import net.lemnik.eodsql.QueryTool;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.ProjectRelationType;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.RelationHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.person.Person;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.space.SpacePermId;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class ProjectHistoryRelation extends HistoryRelation
+{
+
+    public ProjectHistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        super(context, entityIds, fetchOptions);
+    }
+
+    @Override
+    protected List<HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds)
+    {
+        return null;
+    }
+
+    @Override
+    protected List<? extends HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds)
+    {
+        ProjectQuery query = QueryTool.getManagedQuery(ProjectQuery.class);
+        return query.getRelationshipsHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected RelationHistoryEntry createRelationshipEntry(HistoryRelationshipRecord record, Map<Long, Person> authorMap)
+    {
+        RelationHistoryEntry entry = super.createRelationshipEntry(record, authorMap);
+
+        ProjectRelationshipRecord projectRecord = (ProjectRelationshipRecord) record;
+
+        if (projectRecord.spaceId != null)
+        {
+            entry.setRelationType(ProjectRelationType.SPACE);
+            entry.setRelatedObjectId(new SpacePermId(projectRecord.relatedObjectId));
+        } else if (projectRecord.experimentId != null)
+        {
+            entry.setRelationType(ProjectRelationType.EXPERIMENT);
+            entry.setRelatedObjectId(new ExperimentPermId(projectRecord.relatedObjectId));
+        }
+
+        return entry;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectQuery.java
new file mode 100644
index 00000000000..f9bf19d15ef
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectQuery.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.project.sql;
+
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import java.util.List;
+
+import net.lemnik.eodsql.Select;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectQuery;
+import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
+
+/**
+ * @author pkupczyk
+ */
+public interface ProjectQuery extends ObjectQuery
+{
+
+    @Select(sql = "select prh.main_proj_id as entityId, prh.pers_id_author as authorId, prh.relation_type as relationType, "
+            + "prh.entity_perm_id as relatedObjectId, prh.valid_from_timestamp as validFrom, prh.valid_until_timestamp as validTo, "
+            + "prh.space_id as spaceId, prh.expe_id as experimentId "
+            + "from project_relationships_history prh where prh.valid_until_timestamp is not null and prh.main_proj_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<ProjectRelationshipRecord> getRelationshipsHistory(LongSet projectIds);
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectRelationshipRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectRelationshipRecord.java
new file mode 100644
index 00000000000..7822199a732
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/project/sql/ProjectRelationshipRecord.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.project.sql;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class ProjectRelationshipRecord extends HistoryRelationshipRecord
+{
+
+    public Long spaceId;
+
+    public Long experimentId;
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
index ce9e3c4902f..59fbbf208a6 100644
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/SampleTranslator.java
@@ -8,6 +8,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -20,10 +21,10 @@ import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.attachment.IAttachmentTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.dataset.IDataSetTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.experiment.IExperimentTranslator;
-import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.IHistoryTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.material.IMaterialPropertyTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.person.IPersonTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.property.IPropertyTranslator;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.sample.sql.SampleHistoryRelation;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.space.ISpaceTranslator;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.tag.ITagTranslator;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.dataset.DataSet;
@@ -71,9 +72,6 @@ public class SampleTranslator extends AbstractCachingTranslator<SamplePE, Sample
     @Autowired
     private IDataSetTranslator dataSetTranslator;
 
-    @Autowired
-    private IHistoryTranslator historyTranslator;
-
     @Override
     protected boolean shouldTranslate(TranslationContext context, SamplePE input, SampleFetchOptions fetchOptions)
     {
@@ -109,6 +107,16 @@ public class SampleTranslator extends AbstractCachingTranslator<SamplePE, Sample
             relations.add(new SampleParentsRelation(context, samples, fetchOptions.withParents()));
         }
 
+        if (fetchOptions.hasHistory())
+        {
+            Set<Long> sampleIds = new HashSet<Long>();
+            for (SamplePE sample : samples)
+            {
+                sampleIds.add(sample.getId());
+            }
+            relations.add(createRelation(SampleHistoryRelation.class, context, sampleIds, fetchOptions.withHistory()));
+        }
+
         return relations;
     }
 
@@ -205,7 +213,8 @@ public class SampleTranslator extends AbstractCachingTranslator<SamplePE, Sample
 
         if (fetchOptions.hasHistory())
         {
-            result.setHistory(historyTranslator.translate(context, samplePe, fetchOptions.withHistory()));
+            SampleHistoryRelation relation = relations.get(SampleHistoryRelation.class);
+            result.setHistory(relation.getRelated(samplePe.getId()));
             result.getFetchOptions().withHistoryUsing(fetchOptions.withHistory());
         }
     }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleHistoryRelation.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleHistoryRelation.java
new file mode 100644
index 00000000000..feed528d776
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleHistoryRelation.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.sample.sql;
+
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Component;
+
+import net.lemnik.eodsql.QueryTool;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelation;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.RelationHistoryEntry;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.SampleRelationType;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.person.Person;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.dataset.DataSetPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.experiment.ExperimentPermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.sample.SamplePermId;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.id.space.SpacePermId;
+import ch.systemsx.cisd.openbis.generic.shared.dto.RelationType;
+
+/**
+ * @author pkupczyk
+ */
+@Component
+@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+public class SampleHistoryRelation extends HistoryRelation
+{
+
+    public SampleHistoryRelation(TranslationContext context, Collection<Long> entityIds, HistoryEntryFetchOptions fetchOptions)
+    {
+        super(context, entityIds, fetchOptions);
+    }
+
+    @Override
+    protected List<HistoryPropertyRecord> loadPropertyHistory(Collection<Long> entityIds)
+    {
+        SampleQuery query = QueryTool.getManagedQuery(SampleQuery.class);
+        return query.getPropertiesHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected List<? extends HistoryRelationshipRecord> loadRelationshipHistory(Collection<Long> entityIds)
+    {
+        SampleQuery query = QueryTool.getManagedQuery(SampleQuery.class);
+        return query.getRelationshipsHistory(new LongOpenHashSet(entityIds));
+    }
+
+    @Override
+    protected RelationHistoryEntry createRelationshipEntry(HistoryRelationshipRecord record, Map<Long, Person> authorMap)
+    {
+        RelationHistoryEntry entry = super.createRelationshipEntry(record, authorMap);
+
+        SampleRelationshipRecord sampleRecord = (SampleRelationshipRecord) record;
+
+        if (sampleRecord.spaceId != null)
+        {
+            entry.setRelationType(SampleRelationType.SPACE);
+            entry.setRelatedObjectId(new SpacePermId(sampleRecord.relatedObjectId));
+        } else if (sampleRecord.experimentId != null)
+        {
+            entry.setRelationType(SampleRelationType.EXPERIMENT);
+            entry.setRelatedObjectId(new ExperimentPermId(sampleRecord.relatedObjectId));
+        } else if (sampleRecord.sampleId != null)
+        {
+            RelationType relationType = RelationType.valueOf(sampleRecord.relationType);
+
+            switch (relationType)
+            {
+                case PARENT:
+                    entry.setRelationType(SampleRelationType.CHILD);
+                    break;
+                case CHILD:
+                    entry.setRelationType(SampleRelationType.PARENT);
+                    break;
+                case CONTAINER:
+                    entry.setRelationType(SampleRelationType.CONTAINED);
+                    break;
+                case CONTAINED:
+                case COMPONENT:
+                    entry.setRelationType(SampleRelationType.CONTAINER);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unsupported relation type: " + relationType);
+            }
+            entry.setRelatedObjectId(new SamplePermId(sampleRecord.relatedObjectId));
+        } else if (sampleRecord.dataSetId != null)
+        {
+            entry.setRelationType(SampleRelationType.DATA_SET);
+            entry.setRelatedObjectId(new DataSetPermId(sampleRecord.relatedObjectId));
+        }
+
+        return entry;
+    }
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleQuery.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleQuery.java
new file mode 100644
index 00000000000..a8c0027435a
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleQuery.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.sample.sql;
+
+import it.unimi.dsi.fastutil.longs.LongSet;
+
+import java.util.List;
+
+import net.lemnik.eodsql.Select;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.common.sql.ObjectQuery;
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryPropertyRecord;
+import ch.systemsx.cisd.common.db.mapper.LongSetMapper;
+
+/**
+ * @author pkupczyk
+ */
+public interface SampleQuery extends ObjectQuery
+{
+
+    @Select(sql = "select sph.samp_id as entityId, sph.pers_id_author as authorId, pt.code as propertyCode, sph.value as propertyValue, sph.material as materialPropertyValue, sph.vocabulary_term as vocabularyPropertyValue, sph.valid_from_timestamp as validFrom, sph.valid_until_timestamp as validTo "
+            + "from sample_properties_history sph "
+            + "left join sample_type_property_types stpt on sph.stpt_id = stpt.id "
+            + "left join property_types pt on stpt.prty_id = pt.id "
+            + "where sph.samp_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<HistoryPropertyRecord> getPropertiesHistory(LongSet sampleIds);
+
+    @Select(sql = "select srh.main_samp_id as entityId, srh.pers_id_author as authorId, srh.relation_type as relationType, "
+            + "srh.entity_perm_id as relatedObjectId, srh.valid_from_timestamp as validFrom, srh.valid_until_timestamp as validTo, "
+            + "srh.space_id as spaceId, srh.expe_id as experimentId, srh.samp_id as sampleId, srh.data_id as dataSetId "
+            + "from sample_relationships_history srh where srh.valid_until_timestamp is not null and srh.main_samp_id = any(?{1})", parameterBindings = { LongSetMapper.class }, fetchSize = FETCH_SIZE)
+    public List<SampleRelationshipRecord> getRelationshipsHistory(LongSet sampleIds);
+
+}
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleRelationshipRecord.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleRelationshipRecord.java
new file mode 100644
index 00000000000..cd5470e7c4d
--- /dev/null
+++ b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/entity/sample/sql/SampleRelationshipRecord.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.server.api.v3.translator.entity.sample.sql;
+
+import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.history.sql.HistoryRelationshipRecord;
+
+/**
+ * @author pkupczyk
+ */
+public class SampleRelationshipRecord extends HistoryRelationshipRecord
+{
+
+    public Long spaceId;
+
+    public Long experimentId;
+
+    public Long sampleId;
+
+    public Long dataSetId;
+
+}
diff --git a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/MapExperimentTest.java b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/MapExperimentTest.java
index 0bfcd931e6d..1f19309bad2 100644
--- a/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/MapExperimentTest.java
+++ b/openbis/sourceTest/java/ch/ethz/sis/openbis/systemtest/api/v3/MapExperimentTest.java
@@ -794,7 +794,7 @@ public class MapExperimentTest extends AbstractExperimentTest
         assertEquals(entry.getPropertyValue(), "a description");
     }
 
-    @Test
+    @Test(enabled = false) // SSDM-2292
     public void testMapWithHistoryProject()
     {
         ExperimentCreation creation = new ExperimentCreation();
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/ProjectRelationType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/ProjectRelationType.java
new file mode 100644
index 00000000000..8dee805dddc
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/ProjectRelationType.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 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.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history;
+
+import ch.systemsx.cisd.base.annotation.JsonObject;
+
+/**
+ * @author pkupczyk
+ */
+@JsonObject("dto.entity.history.ProjectRelationType")
+public enum ProjectRelationType implements IRelationType
+{
+
+    SPACE, EXPERIMENT
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/project/Project.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/project/Project.java
index db0cc4740aa..f1b62582d66 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/project/Project.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/project/Project.java
@@ -17,6 +17,7 @@ package ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.project;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.attachment.Attachment;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.experiment.Experiment;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.history.HistoryEntry;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.IAttachmentsHolder;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.IModificationDateHolder;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.interfaces.IModifierHolder;
@@ -40,7 +41,7 @@ import java.util.List;
  * Class automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
  */
 @JsonObject("dto.entity.project.Project")
-public class Project implements Serializable, ISpaceHolder, IModifierHolder, IModificationDateHolder, IAttachmentsHolder, IRegistratorHolder, IRegistrationDateHolder
+public class Project implements Serializable, IRegistrationDateHolder, IAttachmentsHolder, ISpaceHolder, IModifierHolder, IModificationDateHolder, IRegistratorHolder
 {
     private static final long serialVersionUID = 1L;
 
@@ -68,6 +69,9 @@ public class Project implements Serializable, ISpaceHolder, IModifierHolder, IMo
     @JsonProperty
     private List<Experiment> experiments;
 
+    @JsonProperty
+    private List<HistoryEntry> history;
+
     @JsonProperty
     private Space space;
 
@@ -196,6 +200,26 @@ public class Project implements Serializable, ISpaceHolder, IModifierHolder, IMo
         this.experiments = experiments;
     }
 
+    // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
+    @JsonIgnore
+    public List<HistoryEntry> getHistory()
+    {
+        if (getFetchOptions().hasHistory())
+        {
+            return history;
+        }
+        else
+        {
+            throw new NotFetchedException("History have not been fetched.");
+        }
+    }
+
+    // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
+    public void setHistory(List<HistoryEntry> history)
+    {
+        this.history = history;
+    }
+
     // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
     @JsonIgnore
     @Override
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/project/ProjectFetchOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/project/ProjectFetchOptions.java
index a474bbf5792..cd0e337b9c2 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/project/ProjectFetchOptions.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/project/ProjectFetchOptions.java
@@ -17,6 +17,7 @@ package ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.project;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.attachment.AttachmentFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.experiment.ExperimentFetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.history.HistoryEntryFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.person.PersonFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.space.SpaceFetchOptions;
 import ch.systemsx.cisd.base.annotation.JsonObject;
@@ -34,6 +35,9 @@ public class ProjectFetchOptions implements Serializable
     @JsonProperty
     private ExperimentFetchOptions experiments;
 
+    @JsonProperty
+    private HistoryEntryFetchOptions history;
+
     @JsonProperty
     private SpaceFetchOptions space;
 
@@ -71,6 +75,28 @@ public class ProjectFetchOptions implements Serializable
         return experiments != null;
     }
 
+    // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
+    public HistoryEntryFetchOptions withHistory()
+    {
+        if (history == null)
+        {
+            history = new HistoryEntryFetchOptions();
+        }
+        return history;
+    }
+
+    // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
+    public HistoryEntryFetchOptions withHistoryUsing(HistoryEntryFetchOptions fetchOptions)
+    {
+        return history = fetchOptions;
+    }
+
+    // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
+    public boolean hasHistory()
+    {
+        return history != null;
+    }
+
     // Method automatically generated with {@link ch.ethz.sis.openbis.generic.shared.api.v3.dto.generators.DtoGenerator}
     public SpaceFetchOptions withSpace()
     {
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/generators/Generator.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/generators/Generator.java
index 918372453e2..9fb5687f3d7 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/generators/Generator.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/generators/Generator.java
@@ -349,6 +349,8 @@ public class Generator extends AbstractGenerator
 
         gen.addPluralFetchedField("List<Experiment>", List.class.getName(), "experiments", "Expreiments", ExperimentFetchOptions.class);
         gen.addClassForImport(Experiment.class);
+        gen.addPluralFetchedField("List<HistoryEntry>", List.class.getName(), "history", "History", HistoryEntryFetchOptions.class);
+        gen.addClassForImport(HistoryEntry.class);
 
         addSpace(gen);
         addRegistrator(gen);
-- 
GitLab