From 0480667e895254b68e9d837d2eb92fa2ef54f30c Mon Sep 17 00:00:00 2001
From: pkupczyk <pkupczyk>
Date: Thu, 20 Aug 2015 10:59:06 +0000
Subject: [PATCH] SSDM-2294 : V3 AS API - paging and sorting of search results:
 - cache with ehcache - keep original collections in views - toString methods
 for all V3 DTOs

SVN: 34498
---
 .../SearchMaterialSqlMethodExecutor.java      |  74 +++++-
 .../ITranslationContextProvider.java          |  37 ---
 .../TranslationContextProvider.java           |  40 ---
 .../source/java/genericApplicationContext.xml |   2 -
 .../api/v3/dto/entity/dataset/DataSet.java    |   6 +
 .../v3/dto/entity/dataset/DataSetType.java    |   6 +
 .../v3/dto/entity/dataset/ExternalData.java   |   6 +
 .../v3/dto/entity/dataset/FileFormatType.java |   6 +
 .../v3/dto/entity/dataset/LocatorType.java    |   6 +
 .../api/v3/dto/entity/deletion/Deletion.java  |   6 +
 .../dto/entity/experiment/ExperimentType.java |   6 +
 .../v3/dto/entity/history/HistoryEntry.java   |   6 +
 .../api/v3/dto/entity/material/Material.java  |   6 +
 .../v3/dto/entity/material/MaterialType.java  |   6 +
 .../api/v3/dto/entity/person/Person.java      |   6 +
 .../api/v3/dto/entity/project/Project.java    |   6 +
 .../api/v3/dto/entity/sample/SampleType.java  |   6 +
 .../shared/api/v3/dto/entity/space/Space.java |   6 +
 .../shared/api/v3/dto/entity/tag/Tag.java     |   6 +
 .../v3/dto/entity/vocabulary/Vocabulary.java  |   6 +
 .../dto/entity/vocabulary/VocabularyTerm.java |   6 +
 ...chOptionsMatchType.java => CacheMode.java} |  27 +-
 .../api/v3/dto/fetchoptions/FetchOptions.java |   6 +-
 .../dto/fetchoptions/FetchOptionsMatcher.java |  95 +------
 .../sort/AbstractCollectionView.java          | 176 +++++++++++++
 .../v3/dto/fetchoptions/sort/ListView.java    | 113 ++++++++
 .../api/v3/dto/fetchoptions/sort/SetView.java |  44 ++++
 .../v3/dto/fetchoptions/sort/SortAndPage.java |  30 +--
 .../api/v3/dto/generators/Generator.java      |  39 +++
 .../fetchoptions/FetchOptionsMatcherTest.java |  44 ++--
 .../fetchoptions/sort/SortAndPageTest.java    | 249 +++++++++---------
 31 files changed, 708 insertions(+), 370 deletions(-)
 delete mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/ITranslationContextProvider.java
 delete mode 100644 openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/TranslationContextProvider.java
 rename openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/{FetchOptionsMatchType.java => CacheMode.java} (56%)
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/AbstractCollectionView.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/ListView.java
 create mode 100644 openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SetView.java

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 e82d7a2da21..0c4b09eba6b 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
@@ -23,6 +23,9 @@ import java.util.List;
 import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.Cache;
+import org.springframework.cache.Cache.ValueWrapper;
+import org.springframework.cache.CacheManager;
 import org.springframework.stereotype.Component;
 
 import ch.ethz.sis.openbis.generic.server.api.v3.executor.IOperationContext;
@@ -30,6 +33,9 @@ import ch.ethz.sis.openbis.generic.server.api.v3.executor.material.ISearchMateri
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.TranslationContext;
 import ch.ethz.sis.openbis.generic.server.api.v3.translator.entity.material.sql.IMaterialSqlTranslator;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.CacheMode;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.FetchOptions;
+import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.FetchOptionsMatcher;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.material.MaterialFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.sort.SortAndPage;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.search.MaterialSearchCriterion;
@@ -47,6 +53,9 @@ public class SearchMaterialSqlMethodExecutor extends AbstractMethodExecutor impl
     @Autowired
     private IMaterialSqlTranslator translator;
 
+    @Autowired
+    private CacheManager cacheManager;
+
     @Override
     public List<Material> search(final String sessionToken, final MaterialSearchCriterion criterion, final MaterialFetchOptions fetchOptions)
     {
@@ -64,20 +73,37 @@ public class SearchMaterialSqlMethodExecutor extends AbstractMethodExecutor impl
     @SuppressWarnings("unchecked")
     private Collection<Material> searchAndTranslate(IOperationContext context, MaterialSearchCriterion criterion, MaterialFetchOptions fetchOptions)
     {
-        if (fetchOptions.getCacheMode() != null)
+        if (CacheMode.NO_CACHE.equals(fetchOptions.getCacheMode()))
+        {
+            return doSearchAndTranslate(context, criterion, fetchOptions);
+        } else if (CacheMode.CACHE.equals(fetchOptions.getCacheMode()) || CacheMode.RELOAD_AND_CACHE.equals(fetchOptions.getCacheMode()))
         {
-            Collection<Material> results = (Collection<Material>) context.getSession().getAttributes().get(getClass().getName() + "_results");
+            Cache cache = cacheManager.getCache("searchCache");
+            CacheKey key = new CacheKey(context.getSession().getSessionToken(), fetchOptions);
+            Collection<Material> results = null;
+
+            if (CacheMode.RELOAD_AND_CACHE.equals(fetchOptions.getCacheMode()))
+            {
+                cache.evict(key);
+            } else
+            {
+                ValueWrapper wrapper = cache.get(key);
+                if (wrapper != null)
+                {
+                    results = (Collection<Material>) wrapper.get();
+                }
+            }
 
             if (results == null)
             {
                 results = doSearchAndTranslate(context, criterion, fetchOptions);
-                context.getSession().getAttributes().put(getClass().getName() + "_results", results);
+                cache.put(key, results);
             }
 
             return results;
         } else
         {
-            return doSearchAndTranslate(context, criterion, fetchOptions);
+            throw new IllegalArgumentException("Unsupported cache mode: " + fetchOptions.getCacheMode());
         }
     }
 
@@ -101,4 +127,44 @@ public class SearchMaterialSqlMethodExecutor extends AbstractMethodExecutor impl
 
         return new ArrayList<Material>(objects);
     }
+
+    private static class CacheKey
+    {
+
+        private String sessionToken;
+
+        private FetchOptions<?> fetchOptions;
+
+        public CacheKey(String sessionToken, FetchOptions<?> fetchOptions)
+        {
+            this.sessionToken = sessionToken;
+            this.fetchOptions = fetchOptions;
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return sessionToken.hashCode() + fetchOptions.getClass().hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+            {
+                return true;
+            }
+            if (obj == null)
+            {
+                return false;
+            }
+            if (getClass() != obj.getClass())
+            {
+                return false;
+            }
+
+            CacheKey other = (CacheKey) obj;
+            return sessionToken.equals(other.sessionToken) && FetchOptionsMatcher.arePartsEqual(fetchOptions, other.fetchOptions);
+        }
+    }
 }
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/ITranslationContextProvider.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/ITranslationContextProvider.java
deleted file mode 100644
index 5d9a6502158..00000000000
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/ITranslationContextProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2013 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;
-
-import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
-
-public interface ITranslationContextProvider
-{
-    /**
-     * Get the translation context for the current session
-     * 
-     * @param session
-     * @param useCache whether to cache the context for later use
-     * @return
-     */
-    TranslationContext getTranslationContext(Session session, boolean useCache);
-
-    /**
-     * Optional call to notify the server that the corresponding session context is not going to be used ever
-     * 
-     * @param session
-     */
-    void discardTranslationContext(Session session);
-}
\ No newline at end of file
diff --git a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/TranslationContextProvider.java b/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/TranslationContextProvider.java
deleted file mode 100644
index bff5dd61f0e..00000000000
--- a/openbis/source/java/ch/ethz/sis/openbis/generic/server/api/v3/translator/TranslationContextProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2013 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;
-
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.stereotype.Component;
-
-import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
-
-@Component
-public class TranslationContextProvider implements ITranslationContextProvider
-{
-    @Override
-    @Cacheable(value = "searchCache", key = "#session.getSessionToken()", condition = "#useCache")
-    public TranslationContext getTranslationContext(Session session, boolean useCache)
-    {
-        return new TranslationContext(session);
-    }
-
-    @Override
-    @CacheEvict(value = "searchCache", key = "#session.getSessionToken()")
-    public void discardTranslationContext(Session session)
-    {
-        // aop noop
-    }
-}
diff --git a/openbis/source/java/genericApplicationContext.xml b/openbis/source/java/genericApplicationContext.xml
index e2c31aabd0b..b838515e8a9 100644
--- a/openbis/source/java/genericApplicationContext.xml
+++ b/openbis/source/java/genericApplicationContext.xml
@@ -424,8 +424,6 @@
 
 	<!-- Caching -->
 	
-	<cache:annotation-driven />
-	
 	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
 		<property name="cacheManager" ref="ehcache" />
 	</bean>
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSet.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSet.java
index 5787b31f18c..d21529a0d11 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSet.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSet.java
@@ -509,4 +509,10 @@ public class DataSet implements Serializable, ITagsHolder, IRegistrationDateHold
         this.materialProperties = materialProperties;
     }
 
+    @Override
+    public String toString()
+    {
+        return "DataSet " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSetType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSetType.java
index 184df422fc9..25e5f20adeb 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSetType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/DataSetType.java
@@ -131,4 +131,10 @@ public class DataSetType implements Serializable, IModificationDateHolder
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "DataSetType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/ExternalData.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/ExternalData.java
index b6ff34ac798..9ba05eef0de 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/ExternalData.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/ExternalData.java
@@ -248,4 +248,10 @@ public class ExternalData implements Serializable
         this.speedHint = speedHint;
     }
 
+    @Override
+    public String toString()
+    {
+        return "ExternalData " + location;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/FileFormatType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/FileFormatType.java
index 009bae1bc49..3112af04102 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/FileFormatType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/FileFormatType.java
@@ -78,4 +78,10 @@ public class FileFormatType implements Serializable
         this.description = description;
     }
 
+    @Override
+    public String toString()
+    {
+        return "FileFormatType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/LocatorType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/LocatorType.java
index b5a4b949def..923545be373 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/LocatorType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/dataset/LocatorType.java
@@ -78,4 +78,10 @@ public class LocatorType implements Serializable
         this.description = description;
     }
 
+    @Override
+    public String toString()
+    {
+        return "LocatorType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/deletion/Deletion.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/deletion/Deletion.java
index cd6dfa99310..065936239a2 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/deletion/Deletion.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/deletion/Deletion.java
@@ -103,4 +103,10 @@ public class Deletion implements Serializable
         this.deletedObjects = deletedObjects;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Deletion " + id;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/experiment/ExperimentType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/experiment/ExperimentType.java
index 15626352f01..c6944356759 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/experiment/ExperimentType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/experiment/ExperimentType.java
@@ -114,4 +114,10 @@ public class ExperimentType implements Serializable, IModificationDateHolder
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "ExperimentType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/HistoryEntry.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/HistoryEntry.java
index ac88c8256e4..ed133dbd441 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/HistoryEntry.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/history/HistoryEntry.java
@@ -103,4 +103,10 @@ public class HistoryEntry implements Serializable
         this.author = author;
     }
 
+    @Override
+    public String toString()
+    {
+        return "HistoryEntry from: " + validFrom + ", to: " + validTo;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/Material.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/Material.java
index e35e63d6b2b..787dcd8959e 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/Material.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/Material.java
@@ -269,4 +269,10 @@ public class Material implements Serializable, ITagsHolder, IRegistrationDateHol
         this.tags = tags;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Material " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/MaterialType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/MaterialType.java
index 124e888b59b..e361711b079 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/MaterialType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/material/MaterialType.java
@@ -114,4 +114,10 @@ public class MaterialType implements Serializable, IModificationDateHolder
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "MaterialType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/person/Person.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/person/Person.java
index 2b2d8f71471..26270a0bfe3 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/person/Person.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/person/Person.java
@@ -214,4 +214,10 @@ public class Person implements Serializable, IRegistrationDateHolder, ISpaceHold
         this.registrator = registrator;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Person " + userId;
+    }
+
 }
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 f1b62582d66..9de55398d9c 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
@@ -324,4 +324,10 @@ public class Project implements Serializable, IRegistrationDateHolder, IAttachme
         this.attachments = attachments;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Project " + permId;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/sample/SampleType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/sample/SampleType.java
index 8eda4563302..749544fa6f6 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/sample/SampleType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/sample/SampleType.java
@@ -194,4 +194,10 @@ public class SampleType implements Serializable, IModificationDateHolder
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "SampleType " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/space/Space.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/space/Space.java
index e408e165032..1c0368b9033 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/space/Space.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/space/Space.java
@@ -189,4 +189,10 @@ public class Space implements Serializable, IRegistrationDateHolder, IRegistrato
         this.projects = projects;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Space " + permId;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/tag/Tag.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/tag/Tag.java
index 12c0eb76873..8fd851982c9 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/tag/Tag.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/tag/Tag.java
@@ -154,4 +154,10 @@ public class Tag implements Serializable, IRegistrationDateHolder
         this.owner = owner;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Tag " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/Vocabulary.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/Vocabulary.java
index 092e381364e..201183d2a8b 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/Vocabulary.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/Vocabulary.java
@@ -141,4 +141,10 @@ public class Vocabulary implements Serializable, IRegistrationDateHolder, IModif
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "Vocabulary " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/VocabularyTerm.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/VocabularyTerm.java
index ab6b6883667..0c723dd22f4 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/VocabularyTerm.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/entity/vocabulary/VocabularyTerm.java
@@ -213,4 +213,10 @@ public class VocabularyTerm implements Serializable, IRegistrationDateHolder, IM
         this.modificationDate = modificationDate;
     }
 
+    @Override
+    public String toString()
+    {
+        return "VocabularyTerm " + code;
+    }
+
 }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatchType.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/CacheMode.java
similarity index 56%
rename from openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatchType.java
rename to openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/CacheMode.java
index eca4466e71e..cb2c711b239 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatchType.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/CacheMode.java
@@ -16,27 +16,12 @@
 
 package ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions;
 
-public enum FetchOptionsMatchType
+/**
+ * @author pkupczyk
+ */
+public enum CacheMode
 {
 
-    ALL_PARTS_AND_ALL_PAGING_AND_SORTING
-    {
-        @Override
-        public boolean isBetterThan(FetchOptionsMatchType matchType)
-        {
-            return matchType == ALL_PARTS_AND_SUB_PAGING_AND_SORTING;
-        }
-
-    },
-    ALL_PARTS_AND_SUB_PAGING_AND_SORTING
-    {
-        @Override
-        public boolean isBetterThan(FetchOptionsMatchType matchType)
-        {
-            return false;
-        }
-    };
-
-    public abstract boolean isBetterThan(FetchOptionsMatchType matchType);
+    NO_CACHE, CACHE, RELOAD_AND_CACHE
 
-}
\ No newline at end of file
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptions.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptions.java
index 01ef5fe2449..0d4835d1250 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptions.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptions.java
@@ -15,7 +15,7 @@ public abstract class FetchOptions<OBJECT> implements Serializable
 
     private Integer from;
 
-    private Integer cacheMode;
+    private CacheMode cacheMode;
 
     @SuppressWarnings("hiding")
     public FetchOptions<OBJECT> count(Integer count)
@@ -41,13 +41,13 @@ public abstract class FetchOptions<OBJECT> implements Serializable
         return from;
     }
 
-    public FetchOptions<OBJECT> cacheMode(Integer mode)
+    public FetchOptions<OBJECT> cacheMode(CacheMode mode)
     {
         this.cacheMode = mode;
         return this;
     }
 
-    public Integer getCacheMode()
+    public CacheMode getCacheMode()
     {
         return cacheMode;
     }
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcher.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcher.java
index 41029031f9b..659225020d2 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcher.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcher.java
@@ -26,51 +26,11 @@ import java.util.Set;
 public class FetchOptionsMatcher
 {
 
-    public static FetchOptionsMatchType match(Object fo1, Object fo2)
-    {
-        if (fo1 == fo2)
-        {
-            return FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING;
-        }
-        if (fo1 == null || fo2 == null)
-        {
-            return null;
-        }
-        if (false == fo1.getClass().equals(fo2.getClass()))
-        {
-            return null;
-        }
-
-        if (arePartsEqual(fo1, fo2) && areSubLevelPagingAndSortingEqual(fo1, fo2))
-        {
-            if (areTopLevelPagingAndSortingEqual(fo1, fo2))
-            {
-                return FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING;
-            } else
-            {
-                return FetchOptionsMatchType.ALL_PARTS_AND_SUB_PAGING_AND_SORTING;
-            }
-        } else
-        {
-            return null;
-        }
-    }
-
-    private static boolean arePartsEqual(Object o1, Object o2)
+    public static boolean arePartsEqual(Object o1, Object o2)
     {
         return areEqual(o1, o2, new PartsMatcher());
     }
 
-    private static boolean areTopLevelPagingAndSortingEqual(Object o1, Object o2)
-    {
-        return areEqual(o1, o2, new TopLevelPagingAndSortingMatcher());
-    }
-
-    private static boolean areSubLevelPagingAndSortingEqual(Object o1, Object o2)
-    {
-        return areEqual(o1, o2, new SubLevelPagingAndSortingMatcher());
-    }
-
     private static boolean areEqual(Object o1, Object o2, Matcher matcher)
     {
         return areEqual(o1, o2, matcher, 0, new HashSet<Pair>());
@@ -166,59 +126,6 @@ public class FetchOptionsMatcher
 
     }
 
-    private static abstract class PagingAndSortingMatcher implements Matcher
-    {
-
-        @Override
-        public boolean match(Object o1, Object o2, boolean has1, boolean has2, Object with1, Object with2)
-        {
-            FetchOptions<?> fo1 = o1 instanceof FetchOptions ? (FetchOptions<?>) o1 : null;
-            FetchOptions<?> fo2 = o2 instanceof FetchOptions ? (FetchOptions<?>) o2 : null;
-
-            if (fo1 == null ^ fo2 == null)
-            {
-                return false;
-            }
-
-            if (fo1 != null && fo2 != null)
-            {
-                // TODO compare sorting
-                return areEqual(fo1.getFrom(), fo2.getFrom()) && areEqual(fo1.getCount(), fo2.getCount());
-            } else
-            {
-                return true;
-            }
-        }
-
-        private boolean areEqual(Object o1, Object o2)
-        {
-            return o1 == null ? o2 == null : o1.equals(o2);
-        }
-
-    }
-
-    private static class TopLevelPagingAndSortingMatcher extends PagingAndSortingMatcher
-    {
-
-        @Override
-        public boolean shouldMatch(int level)
-        {
-            return level == 0;
-        }
-
-    }
-
-    private static class SubLevelPagingAndSortingMatcher extends PagingAndSortingMatcher
-    {
-
-        @Override
-        public boolean shouldMatch(int level)
-        {
-            return level > 0;
-        }
-
-    }
-
     private static class Pair
     {
 
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/AbstractCollectionView.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/AbstractCollectionView.java
new file mode 100644
index 00000000000..a27f8bf59b3
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/AbstractCollectionView.java
@@ -0,0 +1,176 @@
+/*
+ * 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.fetchoptions.sort;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author pkupczyk
+ */
+public abstract class AbstractCollectionView<T> implements Collection<T>, Serializable
+{
+
+    private static final long serialVersionUID = 1L;
+
+    private transient Collection<T> originalCollection;
+
+    private Collection<T> limitedCollection;
+
+    public AbstractCollectionView(Collection<T> originalCollection, Integer from, Integer count)
+    {
+        this.originalCollection = originalCollection;
+        this.limitedCollection = createLimited(originalCollection, from, count);
+    }
+
+    @SuppressWarnings("hiding")
+    protected abstract Collection<T> createLimited(Collection<T> originalCollection, Integer fromOrNull, Integer countOrNull);
+
+    protected static <T> void copyItems(Collection<T> fromCollection, Collection<T> toCollection, Integer fromOrNull, Integer countOrNull)
+    {
+        Integer from = fromOrNull;
+        Integer count = countOrNull;
+
+        if (from == null)
+        {
+            from = 0;
+        }
+
+        if (count == null)
+        {
+            count = fromCollection.size();
+        }
+
+        int index = 0;
+        for (T item : fromCollection)
+        {
+            if (index >= from && index < from + count)
+            {
+                toCollection.add(item);
+            }
+            index++;
+        }
+    }
+
+    @Override
+    public int size()
+    {
+        return limitedCollection.size();
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return limitedCollection.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o)
+    {
+        return limitedCollection.contains(o);
+    }
+
+    @Override
+    public Iterator<T> iterator()
+    {
+        return limitedCollection.iterator();
+    }
+
+    @Override
+    public Object[] toArray()
+    {
+        return limitedCollection.toArray();
+    }
+
+    @Override
+    public <R> R[] toArray(R[] a)
+    {
+        return limitedCollection.toArray(a);
+    }
+
+    @Override
+    public boolean add(T e)
+    {
+        return limitedCollection.add(e);
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        return limitedCollection.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c)
+    {
+        return limitedCollection.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends T> c)
+    {
+        return limitedCollection.addAll(c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        return limitedCollection.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        return limitedCollection.retainAll(c);
+    }
+
+    @Override
+    public void clear()
+    {
+        limitedCollection.clear();
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return limitedCollection.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return limitedCollection.hashCode();
+    }
+
+    public Collection<T> getLimitedCollection()
+    {
+        return limitedCollection;
+    }
+
+    @Override
+    public String toString()
+    {
+        return limitedCollection.toString();
+    }
+
+    public Collection<T> getOriginalCollection()
+    {
+        return originalCollection;
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/ListView.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/ListView.java
new file mode 100644
index 00000000000..2eaa1cfcad3
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/ListView.java
@@ -0,0 +1,113 @@
+/*
+ * 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.fetchoptions.sort;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * @author pkupczyk
+ */
+public class ListView<T> extends AbstractCollectionView<T> implements List<T>
+{
+
+    private static final long serialVersionUID = 1L;
+
+    public ListView(List<T> originalList, Integer from, Integer count)
+    {
+        super(originalList, from, count);
+
+    }
+
+    @Override
+    protected Collection<T> createLimited(Collection<T> originalCollection, Integer fromOrNull, Integer countOrNull)
+    {
+        List<T> limited = new ArrayList<T>();
+        copyItems(originalCollection, limited, fromOrNull, countOrNull);
+        return Collections.unmodifiableList(limited);
+    }
+
+    @Override
+    public T get(int index)
+    {
+        return getLimitedCollection().get(index);
+    }
+
+    @Override
+    public T set(int index, T element)
+    {
+        return getLimitedCollection().set(index, element);
+    }
+
+    @Override
+    public void add(int index, T element)
+    {
+        getLimitedCollection().add(index, element);
+    }
+
+    @Override
+    public T remove(int index)
+    {
+        return getLimitedCollection().remove(index);
+    }
+
+    @Override
+    public int indexOf(Object o)
+    {
+        return getLimitedCollection().indexOf(o);
+    }
+
+    @Override
+    public int lastIndexOf(Object o)
+    {
+        return getLimitedCollection().lastIndexOf(o);
+    }
+
+    @Override
+    public ListIterator<T> listIterator()
+    {
+        return getLimitedCollection().listIterator();
+    }
+
+    @Override
+    public ListIterator<T> listIterator(int index)
+    {
+        return getLimitedCollection().listIterator(index);
+    }
+
+    @Override
+    public List<T> subList(int fromIndex, int toIndex)
+    {
+        return getLimitedCollection().subList(fromIndex, toIndex);
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends T> c)
+    {
+        return getLimitedCollection().addAll(index, c);
+    }
+
+    @Override
+    public List<T> getLimitedCollection()
+    {
+        return (List<T>) super.getLimitedCollection();
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SetView.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SetView.java
new file mode 100644
index 00000000000..c7b1778d4a7
--- /dev/null
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SetView.java
@@ -0,0 +1,44 @@
+/*
+ * 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.fetchoptions.sort;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author pkupczyk
+ */
+public class SetView<T> extends AbstractCollectionView<T> implements Set<T>
+{
+    private static final long serialVersionUID = 1L;
+
+    public SetView(Collection<T> originalCollection, Integer from, Integer count)
+    {
+        super(originalCollection, from, count);
+    }
+
+    @Override
+    protected Collection<T> createLimited(Collection<T> originalCollection, Integer fromOrNull, Integer countOrNull)
+    {
+        Set<T> limited = new LinkedHashSet<T>();
+        copyItems(originalCollection, limited, fromOrNull, countOrNull);
+        return Collections.unmodifiableSet(limited);
+    }
+
+}
diff --git a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPage.java b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPage.java
index 93184b90175..6a5d0d6d2d8 100644
--- a/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPage.java
+++ b/openbis_api/source/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPage.java
@@ -22,7 +22,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -42,6 +41,12 @@ public class SortAndPage
     public <T, C extends Collection<T>> C sortAndPage(C objects, FetchOptions fo)
     {
         C newObjects = objects;
+
+        if (objects instanceof AbstractCollectionView)
+        {
+            newObjects = (C) ((AbstractCollectionView) objects).getOriginalCollection();
+        }
+
         newObjects = (C) sort(newObjects, fo);
         newObjects = (C) page(newObjects, fo);
         nest(newObjects, fo);
@@ -101,31 +106,18 @@ public class SortAndPage
 
             if (objects instanceof List)
             {
-                paged = new ArrayList();
+                paged = new ListView((List) objects, fo.getFrom(), fo.getCount());
             } else if (objects instanceof Set)
             {
-                paged = new LinkedHashSet();
+                paged = new SetView(objects, fo.getFrom(), fo.getCount());
+            } else if (objects instanceof Collection)
+            {
+                paged = new ListView((List) objects, fo.getFrom(), fo.getCount());
             } else
             {
                 throw new IllegalArgumentException("Unsupported collection: " + objects.getClass());
             }
 
-            Integer from = fo.getFrom();
-            Integer count = fo.getCount();
-
-            if (from != null && count != null)
-            {
-                int index = 0;
-                for (Object item : objects)
-                {
-                    if (index >= from && index < from + count)
-                    {
-                        paged.add(item);
-                    }
-                    index++;
-                }
-            }
-
             return paged;
         } else
         {
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 04fab4b7fe1..970e793ffd3 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
@@ -121,6 +121,8 @@ public class Generator extends AbstractGenerator
 
         gen.addStringField("generatedCodePrefix");
         addModificationDate(gen);
+        
+        gen.setToStringMethod("\"SampleType \" + code");
 
         return gen;
     }
@@ -189,6 +191,8 @@ public class Generator extends AbstractGenerator
         addDescription(gen);
         addModificationDate(gen);
 
+        gen.setToStringMethod("\"ExperimentType \" + code");
+        
         // TODO add property definitions
 
         // TODO add validation script
@@ -231,6 +235,8 @@ public class Generator extends AbstractGenerator
         addExperiment(gen);
         addSample(gen);
         addProperties(gen);
+        
+        gen.setToStringMethod("\"DataSet \" + code");
 
         return gen;
     }
@@ -245,6 +251,8 @@ public class Generator extends AbstractGenerator
         addDescription(gen);
         addModificationDate(gen);
 
+        gen.setToStringMethod("\"DataSetType \" + code");
+        
         // TODO add property definitions
 
         // TODO add validation script
@@ -266,6 +274,8 @@ public class Generator extends AbstractGenerator
         gen.addBooleanField("presentInArchive");
         gen.addBooleanField("storageConfirmation");
         gen.addSimpleField(Integer.class, "speedHint");
+        
+        gen.setToStringMethod("\"ExternalData \" + location");
 
         return gen;
     }
@@ -276,6 +286,8 @@ public class Generator extends AbstractGenerator
 
         gen.addStringField("code");
         gen.addStringField("description");
+        
+        gen.setToStringMethod("\"FileFormatType \" + code");
 
         return gen;
     }
@@ -286,6 +298,8 @@ public class Generator extends AbstractGenerator
 
         gen.addStringField("code");
         gen.addStringField("description");
+        
+        gen.setToStringMethod("\"LocatorType \" + code");
 
         return gen;
     }
@@ -296,6 +310,9 @@ public class Generator extends AbstractGenerator
         gen.addSimpleField(IDeletionId.class, "id");
         gen.addStringField("reason");
         gen.addPluralFetchedField("List<DeletedObject>", List.class.getName(), "deletedObjects", "Deleted objects", DeletionFetchOptions.class);
+        
+        gen.setToStringMethod("\"Deletion \" + id");
+        
         return gen;
     }
 
@@ -308,6 +325,8 @@ public class Generator extends AbstractGenerator
         addRegistrationDate(gen);
         addRegistrator(gen);
         addModificationDate(gen);
+        
+        gen.setToStringMethod("\"Vocabulary \" + code");
 
         return gen;
     }
@@ -325,6 +344,8 @@ public class Generator extends AbstractGenerator
         addRegistrationDate(gen);
         addRegistrator(gen);
         addModificationDate(gen);
+        
+        gen.setToStringMethod("\"VocabularyTerm \" + code");
 
         return gen;
     }
@@ -343,6 +364,8 @@ public class Generator extends AbstractGenerator
 
         addSpace(gen);
         addRegistrator(gen);
+        
+        gen.setToStringMethod("\"Person \" + userId");
 
         return gen;
     }
@@ -369,6 +392,8 @@ public class Generator extends AbstractGenerator
 
         gen.addFetchedField(Person.class, "leader", "Leader", PersonFetchOptions.class);
         addAttachments(gen);
+        
+        gen.setToStringMethod("\"Project \" + permId");
 
         return gen;
     }
@@ -386,6 +411,9 @@ public class Generator extends AbstractGenerator
         gen.addClassForImport(Sample.class);
         gen.addPluralFetchedField("List<Project>", List.class.getName(), "projects", "Projects", ProjectFetchOptions.class);
         gen.addClassForImport(Project.class);
+        
+        gen.setToStringMethod("\"Space \" + permId");
+        
         return gen;
     }
 
@@ -400,6 +428,8 @@ public class Generator extends AbstractGenerator
         addRegistrationDate(gen);
 
         gen.addFetchedField(Person.class, "owner", "Owner", PersonFetchOptions.class);
+        
+        gen.setToStringMethod("\"Tag \" + code");
 
         return gen;
     }
@@ -419,6 +449,9 @@ public class Generator extends AbstractGenerator
         addModificationDate(gen);
         addProperties(gen);
         addTags(gen);
+        
+        gen.setToStringMethod("\"Material \" + code");
+        
         return gen;
     }
 
@@ -430,6 +463,9 @@ public class Generator extends AbstractGenerator
         addCode(gen);
         addDescription(gen);
         addModificationDate(gen);
+        
+        gen.setToStringMethod("\"MaterialType \" + code");
+        
         return gen;
     }
 
@@ -439,6 +475,9 @@ public class Generator extends AbstractGenerator
         gen.addSimpleField(Date.class, "validFrom");
         gen.addSimpleField(Date.class, "validTo");
         gen.addFetchedField(Person.class, "author", "Author", PersonFetchOptions.class);
+        
+        gen.setToStringMethod("\"HistoryEntry from: \" + validFrom + \", to: \" + validTo");
+        
         return gen;
     }
 
diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcherTest.java b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcherTest.java
index 2e31db57a00..218ff599d01 100644
--- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcherTest.java
+++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/FetchOptionsMatcherTest.java
@@ -19,7 +19,6 @@ package ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.dataset.DataSetFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.material.MaterialFetchOptions;
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.sample.SampleFetchOptions;
 
@@ -27,10 +26,10 @@ public class FetchOptionsMatcherTest
 {
 
     @Test
-    public void testMatchTheSameObjects()
+    public void testPartsTheSameObjects()
     {
         SampleFetchOptions fo = new SampleFetchOptions();
-        assertMatch(fo, fo, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo, fo));
     }
 
     @Test
@@ -38,15 +37,7 @@ public class FetchOptionsMatcherTest
     {
         SampleFetchOptions fo1 = new SampleFetchOptions();
         SampleFetchOptions fo2 = new SampleFetchOptions();
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
-    }
-
-    @Test
-    public void testMatchObjectsOfDifferentTypes()
-    {
-        SampleFetchOptions fo1 = new SampleFetchOptions();
-        DataSetFetchOptions fo2 = new DataSetFetchOptions();
-        assertMatch(fo1, fo2, null);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -60,7 +51,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace();
         fo2.withExperiment();
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -73,7 +64,7 @@ public class FetchOptionsMatcherTest
         SampleFetchOptions fo2 = new SampleFetchOptions();
         fo2.withSpace();
 
-        assertMatch(fo1, fo2, null);
+        Assert.assertFalse(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -87,7 +78,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace();
         fo2.withChildrenUsing(fo2);
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -102,7 +93,7 @@ public class FetchOptionsMatcherTest
         fo2.withExperiment();
         fo2.withChildrenUsing(fo2);
 
-        assertMatch(fo1, fo2, null);
+        Assert.assertFalse(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -124,7 +115,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace();
         fo2.withChildrenUsing(fo2Children);
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -146,7 +137,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace();
         fo2.withChildrenUsing(fo2Children);
 
-        assertMatch(fo1, fo2, null);
+        Assert.assertFalse(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -160,7 +151,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace().withProjects().withAttachments();
         fo2.withChildren().withDataSets().withHistory();
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -174,7 +165,7 @@ public class FetchOptionsMatcherTest
         fo2.withSpace().withProjects().withAttachments();
         fo2.withChildren().withDataSets();
 
-        assertMatch(fo1, fo2, null);
+        Assert.assertFalse(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -186,7 +177,7 @@ public class FetchOptionsMatcherTest
         MaterialFetchOptions fo2 = new MaterialFetchOptions();
         fo2.count(5).from(10);
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -198,7 +189,7 @@ public class FetchOptionsMatcherTest
         MaterialFetchOptions fo2 = new MaterialFetchOptions();
         fo2.from(3).count(7);
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_SUB_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -210,7 +201,7 @@ public class FetchOptionsMatcherTest
         MaterialFetchOptions fo2 = new MaterialFetchOptions();
         fo2.withTags().from(1).count(5);
 
-        assertMatch(fo1, fo2, FetchOptionsMatchType.ALL_PARTS_AND_ALL_PAGING_AND_SORTING);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
     @Test
@@ -222,12 +213,7 @@ public class FetchOptionsMatcherTest
         MaterialFetchOptions fo2 = new MaterialFetchOptions();
         fo2.withTags().from(2).count(6);
 
-        assertMatch(fo1, fo2, null);
-    }
-
-    private void assertMatch(Object o1, Object o2, FetchOptionsMatchType matchType)
-    {
-        Assert.assertEquals(FetchOptionsMatcher.match(o1, o2), matchType);
+        Assert.assertTrue(FetchOptionsMatcher.arePartsEqual(fo1, fo2));
     }
 
 }
diff --git a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPageTest.java b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPageTest.java
index 0fbd5dfbaf1..344f49e89c4 100644
--- a/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPageTest.java
+++ b/openbis_api/sourceTest/java/ch/ethz/sis/openbis/generic/shared/api/v3/dto/fetchoptions/sort/SortAndPageTest.java
@@ -1,5 +1,6 @@
 package ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.sort;
 
+import static org.testng.AssertJUnit.assertEquals;
 import java.sql.Date;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -8,8 +9,8 @@ import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import ch.ethz.sis.openbis.generic.shared.api.v3.dto.entity.material.Material;
@@ -22,71 +23,71 @@ import ch.ethz.sis.openbis.generic.shared.api.v3.dto.fetchoptions.material.Mater
 public class SortAndPageTest
 {
     @Test
-    public void testSortAndPageTopLevel()
+    public void testTopLevel()
     {
         MaterialFetchOptions fo = new MaterialFetchOptions();
         fo.sortBy().code().desc();
         fo.from(1).count(2);
 
-        Material m1 = new Material();
-        m1.setCode("S1");
-        m1.setFetchOptions(fo);
+        Material material1 = new Material();
+        material1.setCode("M1");
+        material1.setFetchOptions(fo);
 
-        Material m2 = new Material();
-        m2.setCode("S2");
-        m2.setFetchOptions(fo);
+        Material material2 = new Material();
+        material2.setCode("M2");
+        material2.setFetchOptions(fo);
 
-        Material m3 = new Material();
-        m3.setCode("S3");
-        m3.setFetchOptions(fo);
+        Material material3 = new Material();
+        material3.setCode("M3");
+        material3.setFetchOptions(fo);
 
         List<Material> materials = new ArrayList<Material>();
-        materials.add(m1);
-        materials.add(m2);
-        materials.add(m3);
+        materials.add(material1);
+        materials.add(material2);
+        materials.add(material3);
 
         Collection<Material> results = new SortAndPage().sortAndPage(materials, fo);
 
-        assertMaterials(results, Arrays.asList("S2", "S1"));
+        assertEquals(results, list(material2, material1));
     }
 
     @Test
-    public void testSortAndPageSubLevel()
+    public void testSubLevel()
     {
         MaterialFetchOptions fo = new MaterialFetchOptions();
         fo.sortBy().code().desc();
         fo.withTags().from(1).count(1);
 
-        Tag t1 = new Tag();
-        t1.setCode("T1");
-        Tag t2 = new Tag();
-        t2.setCode("T2");
-        Tag t3 = new Tag();
-        t3.setCode("T3");
+        Tag tag1 = new Tag();
+        tag1.setCode("T1");
+        Tag tag2 = new Tag();
+        tag2.setCode("T2");
+        Tag tag3 = new Tag();
+        tag3.setCode("T3");
 
-        Material m1 = new Material();
-        m1.setCode("S1");
-        m1.setTags(new LinkedHashSet<Tag>(Arrays.asList(t1, t2)));
-        m1.setFetchOptions(fo);
+        Material material1 = new Material();
+        material1.setCode("M1");
+        material1.setTags(set(tag1, tag2));
+        material1.setFetchOptions(fo);
 
-        Material m2 = new Material();
-        m2.setCode("S2");
-        m2.setTags(new LinkedHashSet<Tag>(Arrays.asList(t2, t3)));
-        m2.setFetchOptions(fo);
+        Material material2 = new Material();
+        material2.setCode("M2");
+        material2.setTags(set(tag2, tag3));
+        material2.setFetchOptions(fo);
 
         List<Material> materials = new ArrayList<Material>();
-        materials.add(m1);
-        materials.add(m2);
+        materials.add(material1);
+        materials.add(material2);
 
         List<Material> results = new SortAndPage().sortAndPage(materials, fo);
 
-        assertMaterials(results, Arrays.asList("S2", "S1"));
-        assertTags(results.get(0).getTags(), Arrays.asList("T3"));
-        assertTags(results.get(1).getTags(), Arrays.asList("T2"));
+        assertEquals(results, list(material2, material1));
+        assertEquals(results.get(0).getTags(), set(tag3));
+        assertEquals(results.get(1).getTags(), set(tag2));
     }
 
     @Test
-    public void testSortAndPageSubLevelThroughSingleRelation()
+    public void testSubLevelThroughSingleRelation()
     {
         MaterialFetchOptions fo = new MaterialFetchOptions();
         fo.sortBy().code().desc();
@@ -95,11 +96,9 @@ public class SortAndPageTest
         fo.withMaterialProperties().withTags();
 
         Project project1 = new Project();
-        project1.setCode("P1");
         project1.setFetchOptions(fo.withRegistrator().withSpace().withProjects());
 
         Project project2 = new Project();
-        project2.setCode("P2");
         project2.setFetchOptions(fo.withRegistrator().withSpace().withProjects());
 
         Space space1 = new Space();
@@ -107,63 +106,61 @@ public class SortAndPageTest
         space1.setFetchOptions(fo.withRegistrator().withSpace());
 
         Person person1 = new Person();
-        person1.setLastName("Foo");
         person1.setSpace(space1);
         person1.setFetchOptions(fo.withRegistrator());
 
-        Tag t1 = new Tag();
-        t1.setCode("T1");
-        Tag t2 = new Tag();
-        t2.setCode("T2");
-        Tag t3 = new Tag();
-        t3.setCode("T3");
-
-        Material m1 = new Material();
-        m1.setCode("M1");
-        m1.setTags(new LinkedHashSet<Tag>(Arrays.asList(t1, t2)));
-        m1.setRegistrator(person1);
-        m1.setFetchOptions(fo);
-
-        Material m2 = new Material();
-        m2.setCode("M2");
-        m2.setTags(new LinkedHashSet<Tag>(Arrays.asList(t2, t3)));
-        m2.setRegistrator(person1);
-        m2.setFetchOptions(fo);
-
-        Material m3 = new Material();
-        m3.setCode("M3");
-        m3.setTags(new LinkedHashSet<Tag>(Arrays.asList(t1, t3)));
-        m3.setFetchOptions(fo);
+        Tag tag1 = new Tag();
+        tag1.setCode("T1");
+        Tag tag2 = new Tag();
+        tag2.setCode("T2");
+        Tag tag3 = new Tag();
+        tag3.setCode("T3");
+
+        Material material1 = new Material();
+        material1.setCode("M1");
+        material1.setTags(set(tag1, tag2));
+        material1.setRegistrator(person1);
+        material1.setFetchOptions(fo);
+
+        Material material2 = new Material();
+        material2.setCode("M2");
+        material2.setTags(set(tag2, tag3));
+        material2.setRegistrator(person1);
+        material2.setFetchOptions(fo);
+
+        Material material3 = new Material();
+        material3.setCode("M3");
+        material3.setTags(set(tag1, tag3));
+        material3.setFetchOptions(fo);
 
         Map<String, Material> properties1 = new HashMap<String, Material>();
-        properties1.put("PROPERTY_1", m1);
-        properties1.put("PROPERTY_3", m3);
-        m1.setMaterialProperties(properties1);
+        properties1.put("PROPERTY_1", material1);
+        properties1.put("PROPERTY_3", material3);
+        material1.setMaterialProperties(properties1);
 
         Map<String, Material> properties2 = new HashMap<String, Material>();
-        properties2.put("PROPERTY_2", m2);
-        properties2.put("PROPERTY_3", m3);
-        m2.setMaterialProperties(properties2);
+        properties2.put("PROPERTY_2", material2);
+        properties2.put("PROPERTY_3", material3);
+        material2.setMaterialProperties(properties2);
 
         List<Material> materials = new ArrayList<Material>();
-        materials.add(m1);
-        materials.add(m2);
+        materials.add(material1);
+        materials.add(material2);
 
         List<Material> results = new SortAndPage().sortAndPage(materials, fo);
 
-        assertMaterials(results, Arrays.asList("M2", "M1"));
+        assertEquals(results, list(material2, material1));
 
-        Material m1Result = results.get(1);
-        Material m2Result = results.get(0);
+        Material material1Result = results.get(1);
+        Material material2Result = results.get(0);
 
-        assertTags(m1Result.getTags(), Arrays.asList("T2"));
-        assertTags(m1Result.getTags(), Arrays.asList("T2"));
-        assertTags(m2Result.getTags(), Arrays.asList("T3"));
+        assertEquals(material1Result.getTags(), set(tag2));
+        assertEquals(material2Result.getTags(), set(tag3));
 
-        assertProjects(m1Result.getRegistrator().getSpace().getProjects(), Arrays.asList("P2"));
-        assertProjects(m2Result.getRegistrator().getSpace().getProjects(), Arrays.asList("P2"));
+        assertEquals(material1Result.getRegistrator().getSpace().getProjects(), list(project2));
+        assertEquals(material2Result.getRegistrator().getSpace().getProjects(), list(project2));
 
-        assertTags(m2Result.getMaterialProperties().get("PROPERTY_3").getTags(), Arrays.asList("T1", "T3"));
+        assertEquals(material2Result.getMaterialProperties().get("PROPERTY_3").getTags(), set(tag1, tag3));
     }
 
     @Test
@@ -173,67 +170,77 @@ public class SortAndPageTest
         fo.sortBy().code().desc();
         fo.sortBy().registrationDate().asc();
 
-        Material m1 = new Material();
-        m1.setCode("S1");
-        m1.setRegistrationDate(new Date(3));
-        m1.setFetchOptions(fo);
+        Material material1 = new Material();
+        material1.setCode("M1");
+        material1.setRegistrationDate(new Date(3));
+        material1.setFetchOptions(fo);
 
-        Material m2 = new Material();
-        m2.setCode("S1");
-        m2.setRegistrationDate(new Date(2));
-        m2.setFetchOptions(fo);
+        Material material2 = new Material();
+        material2.setCode("M2");
+        material2.setRegistrationDate(new Date(2));
+        material2.setFetchOptions(fo);
 
-        Material m3 = new Material();
-        m3.setCode("S3");
-        m3.setRegistrationDate(new Date(1));
-        m3.setFetchOptions(fo);
+        Material material3 = new Material();
+        material3.setCode("M3");
+        material3.setRegistrationDate(new Date(1));
+        material3.setFetchOptions(fo);
 
         List<Material> materials = new ArrayList<Material>();
-        materials.add(m1);
-        materials.add(m2);
-        materials.add(m3);
+        materials.add(material1);
+        materials.add(material2);
+        materials.add(material3);
 
         List<Material> results = new SortAndPage().sortAndPage(materials, fo);
 
-        Assert.assertEquals(results.get(0), m3);
-        Assert.assertEquals(results.get(1), m2);
-        Assert.assertEquals(results.get(2), m1);
+        assertEquals(results.get(0), material3);
+        assertEquals(results.get(1), material2);
+        assertEquals(results.get(2), material1);
     }
 
-    private void assertMaterials(Collection<Material> results, Collection<String> codes)
+    @Test
+    public void testSamePageMultipleTimes()
     {
-        Collection<String> actualCodes = new ArrayList<String>();
+        Tag tag1 = new Tag();
+        tag1.setCode("T1");
+        Tag tag2 = new Tag();
+        tag2.setCode("T2");
+        Tag tag3 = new Tag();
+        tag3.setCode("T3");
 
-        for (Material result : results)
-        {
-            actualCodes.add(result.getCode());
-        }
+        MaterialFetchOptions fo = new MaterialFetchOptions();
+        fo.from(1).count(1);
+        fo.withTags().from(1).count(1);
 
-        Assert.assertEquals(actualCodes, codes);
-    }
+        Material material1 = new Material();
+        material1.setCode("M1");
+        material1.setTags(set(tag1, tag3));
+        material1.setFetchOptions(fo);
 
-    private void assertTags(Collection<Tag> results, Collection<String> codes)
-    {
-        Collection<String> actualCodes = new ArrayList<String>();
+        Material material2 = new Material();
+        material2.setCode("M2");
+        material2.setTags(set(tag2, tag3));
+        material2.setFetchOptions(fo);
 
-        for (Tag result : results)
-        {
-            actualCodes.add(result.getCode());
-        }
+        List<Material> materials = new ArrayList<Material>();
+        materials.add(material1);
+        materials.add(material2);
 
-        Assert.assertEquals(actualCodes, codes);
+        List<Material> results = new SortAndPage().sortAndPage(materials, fo);
+
+        assertEquals(results, list(material2));
+        assertEquals(results.get(0).getTags(), set(tag3));
     }
 
-    private void assertProjects(Collection<Project> results, Collection<String> codes)
+    @SuppressWarnings("unchecked")
+    private <T> List<T> list(T... items)
     {
-        Collection<String> actualCodes = new ArrayList<String>();
-
-        for (Project result : results)
-        {
-            actualCodes.add(result.getCode());
-        }
+        return Arrays.asList(items);
+    }
 
-        Assert.assertEquals(actualCodes, codes);
+    @SuppressWarnings("unchecked")
+    private <T> Set<T> set(T... items)
+    {
+        return new LinkedHashSet<T>(Arrays.asList(items));
     }
 
 }
-- 
GitLab