From a326ef13594f716eca4cbab9b61351fa8d059e43 Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Tue, 18 Nov 2014 09:07:30 +0000
Subject: [PATCH] SSDM-1080: Extract search domain functionality from
 DataSetTable into new BO class SearchDomainSearcher.

SVN: 32856
---
 .../server/CommonBusinessObjectFactory.java   |   9 +
 .../openbis/generic/server/CommonServer.java  |  10 +-
 .../business/bo/AbstractBusinessObject.java   |  16 +
 .../server/business/bo/DataSetTable.java      | 225 +----------
 .../bo/ICommonBusinessObjectFactory.java      |   2 +
 .../server/business/bo/IDataSetTable.java     |  13 -
 .../business/bo/ISearchDomainSearcher.java    |  44 +++
 .../business/bo/SearchDomainSearcher.java     | 264 +++++++++++++
 .../server/business/bo/DataSetTableTest.java  | 101 -----
 .../business/bo/SearchDomainSearcherTest.java | 374 ++++++++++++++++++
 10 files changed, 718 insertions(+), 340 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ISearchDomainSearcher.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcher.java
 create mode 100644 openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcherTest.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
index 408ba01ecaf..66ea1127e91 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonBusinessObjectFactory.java
@@ -60,6 +60,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IRoleAssignmentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IScriptBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.ISearchDomainSearcher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISpaceBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ITrashBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IVocabularyBO;
@@ -74,6 +75,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.RoleAssignmentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.SampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.SampleTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ScriptBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.SearchDomainSearcher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.SpaceBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.TrashBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.VocabularyBO;
@@ -202,6 +204,13 @@ public final class CommonBusinessObjectFactory extends AbstractBusinessObjectFac
                 getManagedPropertyEvaluatorFactory(), getMultiplexer());
     }
 
+    @Override
+    public ISearchDomainSearcher createSearchDomainSearcher(Session session)
+    {
+        return new SearchDomainSearcher(getDaoFactory(), session, getManagedPropertyEvaluatorFactory(), 
+                getDSSFactory());
+    }
+
     @Override
     public IDeletedDataSetTable createDeletedDataSetTable(Session session)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
index 32a64769c13..38d278e49f5 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
@@ -115,6 +115,7 @@ import ch.systemsx.cisd.openbis.generic.server.business.bo.IRoleAssignmentTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleTable;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IScriptBO;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.ISearchDomainSearcher;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ISpaceBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.ITrashBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IVocabularyBO;
@@ -1621,9 +1622,8 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
     {
         Session session = getSession(sessionToken);
 
-        IDataSetTable dataSetTable = businessObjectFactory.createDataSetTable(session);
-        return dataSetTable.searchForDataSetsWithSequences(preferredSearchDomainOrNull, searchString,
-                optionalParametersOrNull);
+        ISearchDomainSearcher searcher = businessObjectFactory.createSearchDomainSearcher(session);
+        return searcher.searchForEntitiesWithSequences(preferredSearchDomainOrNull, searchString, optionalParametersOrNull);
     }
 
     @Override
@@ -1632,8 +1632,8 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
     {
         Session session = getSession(sessionToken);
 
-        IDataSetTable dataSetTable = businessObjectFactory.createDataSetTable(session);
-        return dataSetTable.listAvailableSearchDomains();
+        ISearchDomainSearcher searcher = businessObjectFactory.createSearchDomainSearcher(session);
+        return searcher.listAvailableSearchDomains();
     }
 
     @Override
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
index 40328ce3664..d4b768b5e05 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/AbstractBusinessObject.java
@@ -25,10 +25,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.lang.StringUtils;
 import org.hibernate.SessionFactory;
 import org.springframework.dao.DataAccessException;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.EntityPropertiesConverter;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAttachmentDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IAuthorizationGroupDAO;
@@ -71,11 +73,13 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.RelationshipUtils;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.ICodeSequenceDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.db.IPermIdDAO;
 import ch.systemsx.cisd.openbis.generic.server.util.SpaceIdentifierHelper;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Identifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.NewAttachment;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentHolderPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.AttachmentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityPropertyPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.EntityTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.IEntityPropertiesHolder;
@@ -207,6 +211,18 @@ abstract class AbstractBusinessObject implements IDAOFactory
         throw UserFailureException.fromTemplate(ERR_MODIFIED_ENTITY, entityName);
     }
 
+    protected static IDataStoreService tryGetDataStoreService(DataStorePE dataStore, 
+            IDataStoreServiceFactory dataStoreServiceFactory)
+    {
+        String remoteURL = dataStore.getRemoteUrl();
+        if (StringUtils.isBlank(remoteURL))
+        {
+            // null if DSS URL has not been specified
+            return null;
+        }
+        return dataStoreServiceFactory.create(remoteURL);
+    }
+
     /**
      * Returns the perm ID of specified identifier or creates a new one if it is <code>null</code>.
      */
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java
index ca1b3c7a9be..b07c0e87967 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTable.java
@@ -21,7 +21,6 @@ import static ch.systemsx.cisd.openbis.generic.shared.translator.DataSetTranslat
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -39,8 +38,6 @@ import org.springframework.dao.DataIntegrityViolationException;
 
 import ch.rinn.restrictions.Private;
 import ch.systemsx.cisd.common.collection.CollectionUtils;
-import ch.systemsx.cisd.common.collection.IKeyExtractor;
-import ch.systemsx.cisd.common.collection.TableMap;
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
@@ -62,13 +59,7 @@ import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDataDAO;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.event.DeleteDataSetEventBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.Constants;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetFileSearchResultLocation;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityPropertySearchResultLocation;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ISearchDomainResultLocation;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomainSearchResult;
 import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant;
-import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TableModelAppender;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TableModelAppender.TableModelWithDifferentColumnCountException;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TableModelAppender.TableModelWithDifferentColumnIdsException;
@@ -81,7 +72,6 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetBatchUpdateDetai
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataStoreServiceKind;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LinkModel;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetBatchUpdatesDTO;
@@ -98,8 +88,6 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
 import ch.systemsx.cisd.openbis.generic.shared.dto.properties.EntityKind;
 import ch.systemsx.cisd.openbis.generic.shared.managed_property.IManagedPropertyEvaluatorFactory;
 import ch.systemsx.cisd.openbis.generic.shared.translator.DataSetTranslator;
-import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTranslator;
-import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTranslator;
 import ch.systemsx.cisd.openbis.generic.shared.util.HibernateUtils;
 
 /**
@@ -116,16 +104,6 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             DataSetTable.class);
 
-    private static final IKeyExtractor<String, IEntityInformationHolderWithPermId> PERM_ID_EXTRACTOR =
-            new IKeyExtractor<String, IEntityInformationHolderWithPermId>()
-                {
-                    @Override
-                    public String getKey(IEntityInformationHolderWithPermId e)
-                    {
-                        return e.getPermId();
-                    }
-                };
-
     @Private
     static final String UPLOAD_COMMENT_TEXT = "Uploaded zip file contains the following data sets:";
 
@@ -293,190 +271,6 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
         this.dataSets = dataSets;
     }
 
-    @Override
-    public List<SearchDomainSearchResultWithFullEntity> searchForDataSetsWithSequences(String preferredSequenceDatabaseOrNull,
-            String sequenceSnippet, Map<String, String> optionalParametersOrNull)
-    {
-        List<SearchDomainSearchResult> searchResults = askAllDataStoreServers(preferredSequenceDatabaseOrNull,
-                sequenceSnippet, optionalParametersOrNull);
-        return enrichWithEntities(searchResults);
-    }
-    
-    private List<SearchDomainSearchResultWithFullEntity> enrichWithEntities(List<SearchDomainSearchResult> searchResults)
-    {
-        Map<EntityLoader, List<String>> map = separate(searchResults);
-        Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> result = loadEntities(map);
-        List<SearchDomainSearchResultWithFullEntity> filteredResult = new ArrayList<SearchDomainSearchResultWithFullEntity>();
-        for (SearchDomainSearchResult searchResult : searchResults)
-        {
-            Selector selector = new Selector(searchResult.getResultLocation());
-            EntityLoader loader = selector.getLoader();
-            IEntityInformationHolderWithPermId entity = result.get(loader).getOrDie(selector.getPermId());
-            SearchDomainSearchResultWithFullEntity searchResultWithEntity = new SearchDomainSearchResultWithFullEntity();
-            searchResultWithEntity.setSearchResult(searchResult);
-            searchResultWithEntity.setEntity(entity);
-            filteredResult.add(searchResultWithEntity);
-        }
-        return filteredResult;
-    }
-
-    private Map<EntityLoader, List<String>> separate(List<SearchDomainSearchResult> searchResults)
-    {
-        Map<EntityLoader, List<String>> map = new EnumMap<DataSetTable.EntityLoader, List<String>>(EntityLoader.class);
-        for (SearchDomainSearchResult searchResult : searchResults)
-        {
-            ISearchDomainResultLocation resultLocation = searchResult.getResultLocation();
-            Selector selector = new Selector(resultLocation);
-            EntityLoader loader = selector.getLoader();
-            List<String> list = map.get(loader);
-            if (list == null)
-            {
-                list = new ArrayList<String>();
-                map.put(loader, list);
-            }
-            list.add(selector.getPermId());
-        }
-        return map;
-    }
-
-    private Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> loadEntities(Map<EntityLoader, List<String>> map)
-    {
-        Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> result =
-                new EnumMap<DataSetTable.EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>>(EntityLoader.class);
-        Set<Entry<EntityLoader, List<String>>> entrySet = map.entrySet();
-        for (Entry<EntityLoader, List<String>> entry : entrySet)
-        {
-            EntityLoader loader = entry.getKey();
-            List<String> permIds = entry.getValue();
-            List<IEntityInformationHolderWithPermId> entities = loader.loadEntities(this, managedPropertyEvaluatorFactory, permIds);
-            result.put(loader, new TableMap<String, IEntityInformationHolderWithPermId>(entities, PERM_ID_EXTRACTOR));
-        }
-        return result;
-    }
-
-    private static enum EntityLoader
-    {
-        SAMPLE()
-        {
-            @Override
-            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
-                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
-            {
-                return SampleTranslator.translate(daoFactory.getSampleDAO().listByPermID(permIds),
-                        "", Collections.<Long, Set<Metaproject>>emptyMap(), evaluatorFactory);
-            }
-        },
-        DATA_SET()
-        {
-            @Override
-            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
-                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
-            {
-                return DataSetTranslator.translate(daoFactory.getDataDAO().listByCode(new HashSet<String>(permIds)),
-                        "", "", Collections.<Long, Set<Metaproject>>emptyMap(), evaluatorFactory);
-            }
-        },
-        EXPERIMENT()
-        {
-            @Override
-            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
-                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
-            {
-                return ExperimentTranslator.translate(daoFactory.getExperimentDAO().listByPermID(permIds),
-                        "", Collections.<Long, Set<Metaproject>>emptyMap(), evaluatorFactory);
-            }
-        },
-        MATERIAL()
-        {
-            @Override
-            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
-                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
-            {
-                throw new UnsupportedOperationException();
-            }
-        };
-
-        public List<IEntityInformationHolderWithPermId> loadEntities(IDAOFactory daoFactory,
-                IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
-        {
-            List<IEntityInformationHolderWithPermId> result = new ArrayList<IEntityInformationHolderWithPermId>();
-            for (IEntityInformationHolderWithPermId entity : doLoadEntities(daoFactory, evaluatorFactory, permIds))
-            {
-                result.add(entity);
-            }
-            return result;
-        }
-
-        public abstract List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
-                IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds);
-    }
-
-    private static final class Selector
-    {
-        private EntityLoader loader;
-
-        private String permId;
-
-        Selector(ISearchDomainResultLocation resultLocation)
-        {
-            permId = null;
-            loader = EntityLoader.DATA_SET;
-            if (resultLocation instanceof DataSetFileSearchResultLocation)
-            {
-                permId = ((DataSetFileSearchResultLocation) resultLocation).getDataSetCode();
-            } else if (resultLocation instanceof EntityPropertySearchResultLocation)
-            {
-                EntityPropertySearchResultLocation location = (EntityPropertySearchResultLocation) resultLocation;
-                permId = location.getPermId();
-                loader = EntityLoader.valueOf(location.getEntityKind().toString());
-            }
-        }
-
-        EntityLoader getLoader()
-        {
-            return loader;
-        }
-
-        String getPermId()
-        {
-            return permId;
-        }
-        
-    }
-    
-    @Override
-    public List<SearchDomain> listAvailableSearchDomains()
-    {
-        List<SearchDomain> result = new ArrayList<SearchDomain>();
-        List<DataStorePE> stores = getDataStoreDAO().listDataStores();
-        for (DataStorePE dataStore : stores)
-        {
-            IDataStoreService service = tryGetDataStoreService(dataStore);
-            if (service != null)
-            {
-                result.addAll(service.listAvailableSearchDomains(dataStore.getSessionToken()));
-            }
-        }
-        return result;
-    }
-
-    private List<SearchDomainSearchResult> askAllDataStoreServers(String preferredSequenceDatabaseOrNull,
-            String sequenceSnippet, Map<String, String> optionalParametersOrNull)
-    {
-        List<SearchDomainSearchResult> result = new ArrayList<SearchDomainSearchResult>();
-        List<DataStorePE> stores = getDataStoreDAO().listDataStores();
-        for (DataStorePE dataStore : stores)
-        {
-            IDataStoreService service = tryGetDataStoreService(dataStore);
-            if (service != null)
-            {
-                result.addAll(service.searchForDataSetsWithSequences(dataStore.getSessionToken(),
-                        preferredSequenceDatabaseOrNull, sequenceSnippet, optionalParametersOrNull));
-            }
-        }
-        return result;
-    }
-
     @Override
     public void loadByDataSetCodes(List<String> dataSetCodes, boolean withProperties,
             boolean lockForUpdate)
@@ -691,23 +485,12 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
         service.uploadDataSetsToCIFEX(sessionToken, cleanDataSets, context);
     }
 
-    // null if DSS URL has not been specified
-    private IDataStoreService tryGetDataStoreService(DataStorePE dataStore)
-    {
-        String remoteURL = dataStore.getRemoteUrl();
-        if (StringUtils.isBlank(remoteURL))
-        {
-            return null;
-        }
-        return dssFactory.create(remoteURL);
-    }
-
     @Override
     public void processDatasets(String datastoreServiceKey, String datastoreCode,
             List<String> datasetCodes, Map<String, String> parameterBindings)
     {
         DataStorePE dataStore = findDataStore(datastoreCode);
-        IDataStoreService service = tryGetDataStoreService(dataStore);
+        IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
         if (service == null)
         {
             throw createUnknownDataStoreServerException();
@@ -763,7 +546,7 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
                             DataStorePE dataStore = findDataStore(batch.getId());
                             String sessionToken = dataStore.getSessionToken();
                             String userSessionToken = session.getSessionToken();
-                            IDataStoreService service = tryGetDataStoreService(dataStore);
+                            IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
                             parameterBindings.put(Constants.USER_PARAMETER, session.tryGetPerson().getUserId());
                             service.processDatasets(sessionToken, userSessionToken, datastoreServiceKey, batch.getObjects(),
                                     parameterBindings, tryGetLoggedUserId(), tryGetLoggedUserEmail());
@@ -1147,7 +930,7 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
         for (Entry<DataStorePE, List<ExternalDataPE>> entry : datasetsByStore.entrySet())
         {
             DataStorePE dataStore = entry.getKey();
-            IDataStoreService service = tryGetDataStoreService(dataStore);
+            IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
             if (service == null)
             {
                 throw createUnknownDataStoreServerException();
@@ -1381,7 +1164,7 @@ public final class DataSetTable extends AbstractDataSetBusinessObject implements
     public LinkModel retrieveLinkFromDataSet(String key, String datastoreCode, String dataSetCode)
     {
         DataStorePE dataStore = findDataStore(datastoreCode);
-        IDataStoreService service = tryGetDataStoreService(dataStore);
+        IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
         if (service == null)
         {
             throw createUnknownDataStoreServerException();
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
index 487489adc49..5d15adaa6fa 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ICommonBusinessObjectFactory.java
@@ -46,6 +46,8 @@ public interface ICommonBusinessObjectFactory extends IAbstractBussinessObjectFa
     public IDataBO createDataBO(Session session);
 
     public IDataSetTable createDataSetTable(final Session session);
+    
+    public ISearchDomainSearcher createSearchDomainSearcher(Session session);
 
     public IDeletedDataSetTable createDeletedDataSetTable(final Session session);
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IDataSetTable.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IDataSetTable.java
index 86d56e04b7c..afaeb18469a 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IDataSetTable.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/IDataSetTable.java
@@ -19,10 +19,8 @@ package ch.systemsx.cisd.openbis.generic.server.business.bo;
 import java.util.List;
 import java.util.Map;
 
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.LinkModel;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetBatchUpdatesDTO;
@@ -37,17 +35,6 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
  */
 public interface IDataSetTable
 {
-    /**
-     * Searchs for data sets with sequences.
-     */
-    List<SearchDomainSearchResultWithFullEntity> searchForDataSetsWithSequences(String preferredSequenceDatabaseOrNull,
-            String sequenceSnippet, Map<String, String> optionalParametersOrNull);
-    
-    /**
-     * Lists all available sequence databases.
-     */
-    List<SearchDomain> listAvailableSearchDomains();
-
     /**
      * Loads data sets specified by their codes. Data set codes will be ignored if no {@link DataPE} could be found. Properties will be loaded too
      * depending on <var>withProperties</var> value. Optionally if <var>lockForUpdate</var> is <var>true</var> all updates to loaded data sets from
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ISearchDomainSearcher.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ISearchDomainSearcher.java
new file mode 100644
index 00000000000..6f075c9aa49
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/ISearchDomainSearcher.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo;
+
+import java.util.List;
+import java.util.Map;
+
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
+
+/**
+ * Business object for searching in search domains.
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface ISearchDomainSearcher
+{
+    /**
+     * Lists all available sequence databases.
+     */
+    List<SearchDomain> listAvailableSearchDomains();
+
+    /**
+     * Searches for entities with sequences.
+     */
+    List<SearchDomainSearchResultWithFullEntity> searchForEntitiesWithSequences(String preferredSearchDomainOrNull,
+            String sequenceSnippet, Map<String, String> optionalParametersOrNull);
+    
+
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcher.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcher.java
new file mode 100644
index 00000000000..40b520bb424
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcher.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2014 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import ch.systemsx.cisd.common.collection.IKeyExtractor;
+import ch.systemsx.cisd.common.collection.TableMap;
+import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
+import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetFileSearchResultLocation;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityPropertySearchResultLocation;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ISearchDomainResultLocation;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomainSearchResult;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithPermId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Metaproject;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+import ch.systemsx.cisd.openbis.generic.shared.managed_property.IManagedPropertyEvaluatorFactory;
+import ch.systemsx.cisd.openbis.generic.shared.translator.DataSetTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.translator.ExperimentTranslator;
+import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTranslator;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class SearchDomainSearcher extends AbstractBusinessObject implements ISearchDomainSearcher
+{
+    private static final Map<Long, Set<Metaproject>> EMPTY_METAPROJECTS = Collections.<Long, Set<Metaproject>>emptyMap();
+
+    private static final IKeyExtractor<String, IEntityInformationHolderWithPermId> PERM_ID_EXTRACTOR =
+            new IKeyExtractor<String, IEntityInformationHolderWithPermId>()
+                {
+                    @Override
+                    public String getKey(IEntityInformationHolderWithPermId e)
+                    {
+                        return e.getPermId();
+                    }
+                };
+
+    private final IDataStoreServiceFactory dssFactory;
+
+    public SearchDomainSearcher(IDAOFactory daoFactory, Session session, 
+            IManagedPropertyEvaluatorFactory managedPropertyEvaluatorFactory, IDataStoreServiceFactory dssFactory)
+    {
+        super(daoFactory, session, managedPropertyEvaluatorFactory);
+        this.dssFactory = dssFactory;
+    }
+
+    @Override
+    public List<SearchDomain> listAvailableSearchDomains()
+    {
+        List<SearchDomain> result = new ArrayList<SearchDomain>();
+        List<DataStorePE> stores = getDataStoreDAO().listDataStores();
+        for (DataStorePE dataStore : stores)
+        {
+            IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
+            if (service != null)
+            {
+                result.addAll(service.listAvailableSearchDomains(dataStore.getSessionToken()));
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public List<SearchDomainSearchResultWithFullEntity> searchForEntitiesWithSequences(String preferredSearchDomainOrNull, 
+            String sequenceSnippet, Map<String, String> optionalParametersOrNull)
+    {
+        List<SearchDomainSearchResult> searchResults 
+                = askAllDataStoreServers(preferredSearchDomainOrNull, sequenceSnippet, optionalParametersOrNull);
+        return enrichWithEntities(searchResults);
+    }
+
+    private List<SearchDomainSearchResult> askAllDataStoreServers(String preferredSearchDomainOrNull,
+            String sequenceSnippet, Map<String, String> optionalParametersOrNull)
+    {
+        List<SearchDomainSearchResult> result = new ArrayList<SearchDomainSearchResult>();
+        List<DataStorePE> stores = getDataStoreDAO().listDataStores();
+        for (DataStorePE dataStore : stores)
+        {
+            IDataStoreService service = tryGetDataStoreService(dataStore, dssFactory);
+            if (service != null)
+            {
+                result.addAll(service.searchForDataSetsWithSequences(dataStore.getSessionToken(),
+                        preferredSearchDomainOrNull, sequenceSnippet, optionalParametersOrNull));
+            }
+        }
+        return result;
+    }
+
+    private List<SearchDomainSearchResultWithFullEntity> enrichWithEntities(List<SearchDomainSearchResult> searchResults)
+    {
+        Map<EntityLoader, List<String>> map = separate(searchResults);
+        Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> result = loadEntities(map);
+        List<SearchDomainSearchResultWithFullEntity> enrichedResults = new ArrayList<SearchDomainSearchResultWithFullEntity>();
+        for (SearchDomainSearchResult searchResult : searchResults)
+        {
+            Selector selector = new Selector(searchResult.getResultLocation());
+            EntityLoader loader = selector.getLoader();
+            IEntityInformationHolderWithPermId entity = result.get(loader).getOrDie(selector.getPermId());
+            SearchDomainSearchResultWithFullEntity searchResultWithEntity = new SearchDomainSearchResultWithFullEntity();
+            searchResultWithEntity.setSearchResult(searchResult);
+            searchResultWithEntity.setEntity(entity);
+            enrichedResults.add(searchResultWithEntity);
+        }
+        return enrichedResults;
+    }
+
+    private Map<EntityLoader, List<String>> separate(List<SearchDomainSearchResult> searchResults)
+    {
+        Map<EntityLoader, List<String>> map = new EnumMap<EntityLoader, List<String>>(EntityLoader.class);
+        for (SearchDomainSearchResult searchResult : searchResults)
+        {
+            ISearchDomainResultLocation resultLocation = searchResult.getResultLocation();
+            Selector selector = new Selector(resultLocation);
+            EntityLoader loader = selector.getLoader();
+            List<String> list = map.get(loader);
+            if (list == null)
+            {
+                list = new ArrayList<String>();
+                map.put(loader, list);
+            }
+            list.add(selector.getPermId());
+        }
+        return map;
+    }
+
+    private Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> loadEntities(Map<EntityLoader, List<String>> map)
+    {
+        Map<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>> result =
+                new EnumMap<EntityLoader, TableMap<String, IEntityInformationHolderWithPermId>>(EntityLoader.class);
+        Set<Entry<EntityLoader, List<String>>> entrySet = map.entrySet();
+        for (Entry<EntityLoader, List<String>> entry : entrySet)
+        {
+            EntityLoader loader = entry.getKey();
+            List<String> permIds = entry.getValue();
+            List<IEntityInformationHolderWithPermId> entities 
+                    = loader.loadEntities(this, managedPropertyEvaluatorFactory, permIds);
+            result.put(loader, new TableMap<String, IEntityInformationHolderWithPermId>(entities, PERM_ID_EXTRACTOR));
+        }
+        return result;
+    }
+
+    private static enum EntityLoader
+    {
+        SAMPLE()
+        {
+            @Override
+            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
+                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
+            {
+                List<SamplePE> samples = daoFactory.getSampleDAO().listByPermID(permIds);
+                return SampleTranslator.translate(samples, "", EMPTY_METAPROJECTS, evaluatorFactory);
+            }
+        },
+        DATA_SET()
+        {
+            @Override
+            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
+                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
+            {
+                List<DataPE> dataSets = daoFactory.getDataDAO().listByCode(new HashSet<String>(permIds));
+                return DataSetTranslator.translate(dataSets, "", "", EMPTY_METAPROJECTS, evaluatorFactory);
+            }
+        },
+        EXPERIMENT()
+        {
+            @Override
+            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
+                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
+            {
+                List<ExperimentPE> experiments = daoFactory.getExperimentDAO().listByPermID(permIds);
+                return ExperimentTranslator.translate(experiments, "", EMPTY_METAPROJECTS, evaluatorFactory);
+            }
+        },
+        MATERIAL()
+        {
+            @Override
+            public List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
+                    IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
+            {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        public List<IEntityInformationHolderWithPermId> loadEntities(IDAOFactory daoFactory,
+                IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds)
+        {
+            List<IEntityInformationHolderWithPermId> result = new ArrayList<IEntityInformationHolderWithPermId>();
+            for (IEntityInformationHolderWithPermId entity : doLoadEntities(daoFactory, evaluatorFactory, permIds))
+            {
+                result.add(entity);
+            }
+            return result;
+        }
+
+        public abstract List<? extends IEntityInformationHolderWithPermId> doLoadEntities(IDAOFactory daoFactory,
+                IManagedPropertyEvaluatorFactory evaluatorFactory, List<String> permIds);
+    }
+
+    private static final class Selector
+    {
+        private EntityLoader loader;
+
+        private String permId;
+
+        Selector(ISearchDomainResultLocation resultLocation)
+        {
+            permId = null;
+            loader = EntityLoader.DATA_SET;
+            if (resultLocation instanceof DataSetFileSearchResultLocation)
+            {
+                permId = ((DataSetFileSearchResultLocation) resultLocation).getDataSetCode();
+            } else if (resultLocation instanceof EntityPropertySearchResultLocation)
+            {
+                EntityPropertySearchResultLocation location = (EntityPropertySearchResultLocation) resultLocation;
+                permId = location.getPermId();
+                loader = EntityLoader.valueOf(location.getEntityKind().toString());
+            }
+        }
+
+        EntityLoader getLoader()
+        {
+            return loader;
+        }
+
+        String getPermId()
+        {
+            return permId;
+        }
+        
+    }
+    
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java
index b82b8bb38f3..e69c9856d54 100644
--- a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/DataSetTableTest.java
@@ -26,9 +26,7 @@ import static ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetArchiving
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -47,16 +45,12 @@ import ch.systemsx.cisd.openbis.generic.server.business.ManagerTestTool;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.event.DeleteDataSetEventBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.CommonTestUtils;
 import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetFileSearchResultLocation;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
-import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomainSearchResult;
 import ch.systemsx.cisd.openbis.generic.shared.basic.BasicConstant;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Code;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetArchivingStatus;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetUploadContext;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
@@ -82,12 +76,6 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 @Friend(toClasses = DataSetTable.class)
 public final class DataSetTableTest extends AbstractBOTest
 {
-    private static final Map<String, String> OPTIONAL_PARAMETERS = Collections.singletonMap("greeting", "hi");
-
-    private static final String SEQUENCE_SNIPPET = "GATTACA";
-
-    private static final String SEQUENCE_DATABASE = "MY_DB";
-
     private IDataStoreServiceFactory dssFactory;
 
     private DataStorePE dss1;
@@ -165,95 +153,6 @@ public final class DataSetTableTest extends AbstractBOTest
             });
     }
 
-    @Test
-    public void testSearchForDataSetsWithSequences()
-    {
-        DataStorePE store1 = new DataStorePE();
-        store1.setRemoteUrl("http://abc1.de");
-        DataStorePE store2 = new DataStorePE();
-        store2.setRemoteUrl("http://abc2.de");
-        store2.setSessionToken("session-2");
-        DataStorePE store3 = new DataStorePE();
-        prepareListDataStores(store1, store2, store3);
-        prepareSearchSequenceDatabase(store1, dataStoreService1, "ds1", "ds2");
-        prepareSearchSequenceDatabase(store2, dataStoreService2, "ds3", "ds3");
-        final ExternalDataPE ds1 = createDataSet("ds1", store1);
-        final ExternalDataPE ds2 = createDataSet("ds2", store1);
-        final ExternalDataPE ds3 = createDataSet("ds3", store2);
-        context.checking(new Expectations()
-            {
-                {
-                    one(dataDAO).listByCode(new HashSet<String>(Arrays.asList("ds1", "ds2", "ds3")));
-                    will(returnValue(Arrays.asList(new ExternalDataPE[] { ds1, ds2, ds3 })));
-                }
-            });
-
-        List<SearchDomainSearchResultWithFullEntity> results =
-                createDataSetTable().searchForDataSetsWithSequences(SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
-
-        assertEquals("Search Domain: test-db, Result location: [Data set: ds1, path: ds1/path, "
-                + "identifier: [id-ds1], position: 42]",
-                results.get(0).getSearchResult().toString());
-        assertEquals(ds1.getCode(), ((AbstractExternalData) results.get(0).getEntity()).getCode());
-        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(0).getEntity()).getExperiment().getIdentifier());
-        assertEquals("Search Domain: test-db, Result location: [Data set: ds2, path: ds2/path, "
-                + "identifier: [id-ds2], position: 42]",
-                results.get(1).getSearchResult().toString());
-        assertEquals(ds2.getCode(), ((AbstractExternalData) results.get(1).getEntity()).getCode());
-        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(1).getEntity()).getExperiment().getIdentifier());
-        assertEquals("Search Domain: test-db, Result location: [Data set: ds3, path: ds3/path, "
-                + "identifier: [id-ds3], position: 42]",
-                results.get(2).getSearchResult().toString());
-        assertEquals(ds3.getCode(), ((AbstractExternalData) results.get(2).getEntity()).getCode());
-        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(2).getEntity()).getExperiment().getIdentifier());
-        assertEquals(ds3.getCode(), ((AbstractExternalData) results.get(3).getEntity()).getCode());
-        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(3).getEntity()).getExperiment().getIdentifier());
-        assertEquals(4, results.size());
-        context.assertIsSatisfied();
-    }
-
-    private void prepareListDataStores(final DataStorePE... dataStores)
-    {
-        context.checking(new Expectations()
-            {
-                {
-                    one(dataStoreDAO).listDataStores();
-                    will(returnValue(Arrays.asList(dataStores)));
-                }
-            });
-    }
-
-    private void prepareSearchSequenceDatabase(final DataStorePE dataStore, final IDataStoreService service,
-            final String... foundDataSets)
-    {
-        context.checking(new Expectations()
-            {
-                {
-                    allowing(dssFactory).create(dataStore.getRemoteUrl());
-                    will(returnValue(service));
-
-                    one(service).searchForDataSetsWithSequences(dataStore.getSessionToken(),
-                            SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
-                    List<SearchDomainSearchResult> results = new ArrayList<SearchDomainSearchResult>();
-                    for (String foundDataSet : foundDataSets)
-                    {
-                        SearchDomainSearchResult result = new SearchDomainSearchResult();
-                        SearchDomain searchDomain = new SearchDomain();
-                        searchDomain.setName("test-db");
-                        result.setSearchDomain(searchDomain);
-                        DataSetFileSearchResultLocation resultLocation = new DataSetFileSearchResultLocation();
-                        resultLocation.setDataSetCode(foundDataSet);
-                        resultLocation.setPathInDataSet(foundDataSet + "/path");
-                        resultLocation.setPosition(42);
-                        resultLocation.setIdentifier("id-" + foundDataSet);
-                        result.setResultLocation(resultLocation);
-                        results.add(result);
-                    }
-                    will(returnValue(results));
-                }
-            });
-    }
-
     @Test
     public final void testLoadBySampleTechIdWithNullSampleId()
     {
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcherTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcherTest.java
new file mode 100644
index 00000000000..905ca0b3e67
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/server/business/bo/SearchDomainSearcherTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2014 ETH Zuerich, SIS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.server.business.bo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.jmock.Expectations;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.openbis.generic.server.business.IDataStoreServiceFactory;
+import ch.systemsx.cisd.openbis.generic.server.business.ManagerTestTool;
+import ch.systemsx.cisd.openbis.generic.shared.IDataStoreService;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetFileSearchResultLocation;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.EntityPropertySearchResultLocation;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomain;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchDomainSearchResult;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IPermIdHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SearchDomainSearchResultWithFullEntity;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataSetTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExperimentTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.FileFormatTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.ProjectPE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
+import ch.systemsx.cisd.openbis.generic.shared.dto.SpacePE;
+
+/**
+ * @author Franz-Josef Elmer
+ */
+public class SearchDomainSearcherTest extends AbstractBOTest
+{
+    private static final Map<String, String> OPTIONAL_PARAMETERS = Collections.singletonMap("greeting", "hi");
+
+    private static final String SEQUENCE_SNIPPET = "GATTACA";
+
+    private static final String SEQUENCE_DATABASE = "MY_DB";
+
+    private IDataStoreServiceFactory dssFactory;
+
+    private IDataStoreService dataStoreService1;
+
+    private IDataStoreService dataStoreService2;
+
+    private DataStorePE store1;
+
+    private DataStorePE store2;
+
+    private DataStorePE store3;
+
+    @BeforeMethod
+    @Override
+    public void beforeMethod()
+    {
+        super.beforeMethod();
+        dssFactory = context.mock(IDataStoreServiceFactory.class);
+        dataStoreService1 = context.mock(IDataStoreService.class, "dataStoreService1");
+        dataStoreService2 = context.mock(IDataStoreService.class, "dataStoreService2");
+        store1 = new DataStorePE();
+        store1.setRemoteUrl("http://abc1.de");
+        store1.setSessionToken("session-1");
+        store2 = new DataStorePE();
+        store2.setRemoteUrl("http://abc2.de");
+        store2.setSessionToken("session-2");
+        store3 = new DataStorePE();
+        prepareListDataStores(store1, store2, store3);
+        prepareCreateService(dataStoreService1, store1);
+        prepareCreateService(dataStoreService2, store2);
+    }
+
+    @Test
+    public void testListAvailableSearchDomains()
+    {
+        final SearchDomain searchDomain1 = new SearchDomain();
+        final SearchDomain searchDomain2 = new SearchDomain();
+        final SearchDomain searchDomain3 = new SearchDomain();
+        context.checking(new Expectations()
+            {
+                {
+                    one(dataStoreService1).listAvailableSearchDomains(store1.getSessionToken());
+                    will(returnValue(Arrays.asList(searchDomain1)));
+
+                    one(dataStoreService2).listAvailableSearchDomains(store2.getSessionToken());
+                    will(returnValue(Arrays.asList(searchDomain2, searchDomain3)));
+                }
+            });
+
+        List<SearchDomain> searchDomains = createSearcher().listAvailableSearchDomains();
+
+        assertSame(searchDomain1, searchDomains.get(0));
+        assertSame(searchDomain2, searchDomains.get(1));
+        assertSame(searchDomain3, searchDomains.get(2));
+        assertEquals(3, searchDomains.size());
+        context.assertIsSatisfied();
+
+    }
+
+    @Test
+    public void testSearchForEntitiesWithSequences()
+    {
+        prepareSearchForEntityPropertiesWithSequences(store1, dataStoreService1, "DATA_SET:DS1:SEQ");
+        prepareSearchForEntityPropertiesWithSequences(store2, dataStoreService2, 
+                "SAMPLE:S1:OLIGO", "SAMPLE:S2:OLIGO", "EXPERIMENT:E1:S");
+        ExternalDataPE ds1 = createDataSet("DS1", store1);
+        prepareListDataSetsByCode(ds1);
+        SamplePE s1 = createSample("S1");
+        SamplePE s2 = createSample("S2");
+        prepareListSamplesByPermId(s1, s2);
+        ExperimentPE e1 = createExperiment("E1");
+        prepareListExperimentsByPermId(e1);
+
+        List<SearchDomainSearchResultWithFullEntity> results =
+                createSearcher().searchForEntitiesWithSequences(SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
+
+        assertEquals("DS1", results.get(0).getEntity().getPermId());
+        assertEquals("Search Domain: test-db, Result location: [Data_set perm id: DS1, property type: SEQ, position: 24]", 
+                results.get(0).getSearchResult().toString());
+        assertEquals("S1", results.get(1).getEntity().getPermId());
+        assertEquals("Search Domain: test-db, Result location: [Sample perm id: S1, property type: OLIGO, position: 24]", 
+                results.get(1).getSearchResult().toString());
+        assertEquals("S2", results.get(2).getEntity().getPermId());
+        assertEquals("Search Domain: test-db, Result location: [Sample perm id: S2, property type: OLIGO, position: 24]", 
+                results.get(2).getSearchResult().toString());
+        assertEquals("E1", results.get(3).getEntity().getPermId());
+        assertEquals("Search Domain: test-db, Result location: [Experiment perm id: E1, property type: S, position: 24]", 
+                results.get(3).getSearchResult().toString());
+        assertEquals(4, results.size());
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testSearchForDataSetsWithSequences()
+    {
+        prepareSearchForDataSetsWithSequences(store1, dataStoreService1, "ds1", "ds2");
+        prepareSearchForDataSetsWithSequences(store2, dataStoreService2, "ds3", "ds3");
+        final ExternalDataPE ds1 = createDataSet("ds1", store1);
+        final ExternalDataPE ds2 = createDataSet("ds2", store1);
+        final ExternalDataPE ds3 = createDataSet("ds3", store2);
+        prepareListDataSetsByCode(ds1, ds2, ds3);
+
+        List<SearchDomainSearchResultWithFullEntity> results =
+                createSearcher().searchForEntitiesWithSequences(SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
+
+        assertEquals("Search Domain: test-db, Result location: [Data set: ds1, path: ds1/path, "
+                + "identifier: [id-ds1], position: 42]",
+                results.get(0).getSearchResult().toString());
+        assertEquals(ds1.getCode(), ((AbstractExternalData) results.get(0).getEntity()).getCode());
+        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(0).getEntity()).getExperiment().getIdentifier());
+        assertEquals("Search Domain: test-db, Result location: [Data set: ds2, path: ds2/path, "
+                + "identifier: [id-ds2], position: 42]",
+                results.get(1).getSearchResult().toString());
+        assertEquals(ds2.getCode(), ((AbstractExternalData) results.get(1).getEntity()).getCode());
+        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(1).getEntity()).getExperiment().getIdentifier());
+        assertEquals("Search Domain: test-db, Result location: [Data set: ds3, path: ds3/path, "
+                + "identifier: [id-ds3], position: 42]",
+                results.get(2).getSearchResult().toString());
+        assertEquals(ds3.getCode(), ((AbstractExternalData) results.get(2).getEntity()).getCode());
+        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(2).getEntity()).getExperiment().getIdentifier());
+        assertEquals(ds3.getCode(), ((AbstractExternalData) results.get(3).getEntity()).getCode());
+        assertEquals("/G1/P1/exp1", ((AbstractExternalData) results.get(3).getEntity()).getExperiment().getIdentifier());
+        assertEquals(4, results.size());
+        context.assertIsSatisfied();
+    }
+
+    private void prepareListDataStores(final DataStorePE... dataStores)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(dataStoreDAO).listDataStores();
+                    will(returnValue(Arrays.asList(dataStores)));
+                }
+            });
+    }
+
+    private void prepareCreateService(final IDataStoreService service, final DataStorePE... dataStores)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    for (DataStorePE dataStore : dataStores)
+                    {
+                        allowing(dssFactory).create(dataStore.getRemoteUrl());
+                        will(returnValue(service));
+                    }
+                }
+            });
+
+    }
+
+    private void prepareSearchForDataSetsWithSequences(final DataStorePE dataStore, final IDataStoreService service,
+            final String... foundDataSets)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(service).searchForDataSetsWithSequences(dataStore.getSessionToken(),
+                            SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
+                    List<SearchDomainSearchResult> results = new ArrayList<SearchDomainSearchResult>();
+                    for (String foundDataSet : foundDataSets)
+                    {
+                        SearchDomainSearchResult result = new SearchDomainSearchResult();
+                        SearchDomain searchDomain = new SearchDomain();
+                        searchDomain.setName("test-db");
+                        result.setSearchDomain(searchDomain);
+                        DataSetFileSearchResultLocation resultLocation = new DataSetFileSearchResultLocation();
+                        resultLocation.setDataSetCode(foundDataSet);
+                        resultLocation.setPathInDataSet(foundDataSet + "/path");
+                        resultLocation.setPosition(42);
+                        resultLocation.setIdentifier("id-" + foundDataSet);
+                        result.setResultLocation(resultLocation);
+                        results.add(result);
+                    }
+                    will(returnValue(results));
+                }
+            });
+    }
+
+    private void prepareSearchForEntityPropertiesWithSequences(final DataStorePE dataStore,
+            final IDataStoreService service, final String... foundLocations)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(service).searchForDataSetsWithSequences(dataStore.getSessionToken(),
+                            SEQUENCE_DATABASE, SEQUENCE_SNIPPET, OPTIONAL_PARAMETERS);
+                    List<SearchDomainSearchResult> results = new ArrayList<SearchDomainSearchResult>();
+                    for (String foundLocation : foundLocations)
+                    {
+                        SearchDomainSearchResult result = new SearchDomainSearchResult();
+                        SearchDomain searchDomain = new SearchDomain();
+                        searchDomain.setName("test-db");
+                        result.setSearchDomain(searchDomain);
+                        EntityPropertySearchResultLocation resultLocation = new EntityPropertySearchResultLocation();
+                        String[] splittedLocation = foundLocation.split(":");
+                        resultLocation.setEntityKind(EntityKind.valueOf(splittedLocation[0]));
+                        resultLocation.setPermId(splittedLocation[1]);
+                        resultLocation.setPropertyType(splittedLocation[2]);
+                        resultLocation.setPosition(24);
+                        result.setResultLocation(resultLocation);
+                        results.add(result);
+                    }
+                    will(returnValue(results));
+                }
+            });
+    }
+
+    private void prepareListDataSetsByCode(final ExternalDataPE... dataSets)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(dataDAO).listByCode(new LinkedHashSet<String>(getPermIds(dataSets)));
+                    will(returnValue(Arrays.asList(dataSets)));
+                }
+            });
+    }
+
+    private void prepareListSamplesByPermId(final SamplePE... samples)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(sampleDAO).listByPermID(getPermIds(samples));
+                    will(returnValue(Arrays.asList(samples)));
+                }
+            });
+    }
+
+    private void prepareListExperimentsByPermId(final ExperimentPE... experiments)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(experimentDAO).listByPermID(getPermIds(experiments));
+                    will(returnValue(Arrays.asList(experiments)));
+                }
+            });
+    }
+
+    private ExternalDataPE createDataSet(String code, DataStorePE dataStore)
+    {
+        ExternalDataPE data = new ExternalDataPE();
+        data.setId((long) code.hashCode());
+        data.setCode(code);
+        data.setDataStore(dataStore);
+        data.setLocation("here/" + code);
+        ExperimentPE experiment = new ExperimentPE();
+        experiment.setCode("exp1");
+        experiment.setExperimentType(new ExperimentTypePE());
+        ProjectPE project = new ProjectPE();
+        project.setCode("p1");
+        SpacePE group = new SpacePE();
+        group.setCode("g1");
+        project.setSpace(group);
+        experiment.setProject(project);
+        data.setExperiment(experiment);
+        DataSetTypePE type = new DataSetTypePE();
+        type.setDataSetKind(DataSetKind.PHYSICAL.name());
+        data.setDataSetType(type);
+        FileFormatTypePE fileFormatType = new FileFormatTypePE();
+        fileFormatType.setCode("fileFormat");
+        data.setFileFormatType(fileFormatType);
+        return data;
+    }
+    
+    private SamplePE createSample(String permID)
+    {
+        SamplePE sample = new SamplePE();
+        sample.setPermId(permID);
+        SampleTypePE sampleType = new SampleTypePE();
+        sampleType.setCode("TYPE");
+        sampleType.setListable(true);
+        sampleType.setSubcodeUnique(false);
+        sampleType.setAutoGeneratedCode(false);
+        sampleType.setShowParentMetadata(false);
+        sampleType.setContainerHierarchyDepth(0);
+        sampleType.setGeneratedFromHierarchyDepth(0);
+        sample.setSampleType(sampleType);
+        return sample;
+    }
+    
+    private ExperimentPE createExperiment(String permID)
+    {
+        ExperimentPE experiment = new ExperimentPE();
+        experiment.setPermId(permID);
+        experiment.setExperimentType(new ExperimentTypePE());
+        ProjectPE project = new ProjectPE();
+        project.setSpace(new SpacePE());
+        experiment.setProject(project);
+        return experiment;
+    }
+
+    private SearchDomainSearcher createSearcher()
+    {
+        return new SearchDomainSearcher(daoFactory, ManagerTestTool.EXAMPLE_SESSION,
+                managedPropertyEvaluatorFactory, dssFactory);
+    }
+
+    protected List<String> getPermIds(final IPermIdHolder... permIdHolders)
+    {
+        List<String> permIds = new ArrayList<String>();
+        for (IPermIdHolder permIdHolder : permIdHolders)
+        {
+            permIds.add(permIdHolder.getPermId());
+        }
+        return permIds;
+    }
+}
-- 
GitLab