From fd709509bada2ceb36011ce5c153656d4a47549b Mon Sep 17 00:00:00 2001
From: cramakri <cramakri>
Date: Thu, 27 May 2010 07:42:53 +0000
Subject: [PATCH] LMS-1544 Improved performace of listImages and
 listFeatureVectors.

SVN: 16139
---
 .../screening/server/ScreeningServer.java     |   3 +-
 .../logic/FeatureVectorDatasetLoader.java     | 108 ++++++++
 .../server/logic/ImageDatasetLoader.java      |  72 +++++
 .../server/logic/PlateDatasetLoader.java      | 258 ++++++++++++++++++
 .../server/logic/ScreeningApiImpl.java        | 214 +--------------
 .../server/logic/ScreeningUtils.java          |  20 ++
 .../server/logic/ScreeningApiImplTest.java    |  58 ++--
 7 files changed, 499 insertions(+), 234 deletions(-)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/FeatureVectorDatasetLoader.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ImageDatasetLoader.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/PlateDatasetLoader.java

diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
index 88134d36778..8c8f91858ce 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/ScreeningServer.java
@@ -199,7 +199,8 @@ public final class ScreeningServer extends AbstractServer<IScreeningServer> impl
     private ScreeningApiImpl createScreeningApiImpl(String sessionToken)
     {
         final Session session = getSession(sessionToken);
-        return new ScreeningApiImpl(session, businessObjectFactory, getDAOFactory());
+        return new ScreeningApiImpl(session, businessObjectFactory, getDAOFactory(),
+                getDataStoreBaseURL());
     }
 
     public void logoutScreening(String sessionToken)
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/FeatureVectorDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/FeatureVectorDatasetLoader.java
new file mode 100644
index 00000000000..f1fc3a5e4a9
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/FeatureVectorDatasetLoader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.plugin.screening.server.logic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.IDatasetLister;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataStore;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+import ch.systemsx.cisd.openbis.plugin.screening.server.IScreeningBusinessObjectFactory;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.FeatureVectorDatasetReference;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateIdentifier;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+
+/**
+ * @author Chandrasekhar Ramakrishnan
+ */
+class FeatureVectorDatasetLoader extends ImageDatasetLoader
+{
+    // TODO 2010-05-27, CR : See PlateDatasetLoader todo comment
+
+    // Parameter state
+    private final String featureVectorDatasetTypeCode;
+
+    // Running state
+    private List<ExternalData> featureVectorDatasets;
+
+    FeatureVectorDatasetLoader(Session session,
+            IScreeningBusinessObjectFactory businessObjectFactory, String dataStoreBaseURL,
+            List<? extends PlateIdentifier> plates)
+    {
+        super(session, businessObjectFactory, dataStoreBaseURL, plates);
+        featureVectorDatasetTypeCode = ScreeningConstants.IMAGE_ANALYSIS_DATASET_TYPE;
+    }
+
+    public List<FeatureVectorDatasetReference> getFeatureVectorDatasets()
+    {
+        // Load the image data sets
+        load();
+        loadFeatureVectorDatasets();
+        return asFeatureVectorDatasets();
+    }
+
+    private void loadFeatureVectorDatasets()
+    {
+        // TODO 2010-05-26, CR, : This is slow if there are a large number of image data sets
+        // Need to add a query to the dataset lister that returns, for a collection of tech ids, a
+        // child datasets and their parents.
+        featureVectorDatasets = new ArrayList<ExternalData>();
+        IDatasetLister datasetLister =
+                businessObjectFactory.createDatasetLister(session, dataStoreBaseURL);
+
+        for (ExternalData data : getDatasets())
+        {
+            List<ExternalData> children =
+                    datasetLister.listByParentTechId(new TechId(data.getId()));
+            ArrayList<ExternalData> parentList = new ArrayList<ExternalData>();
+            parentList.add(data);
+            for (ExternalData child : children)
+            {
+                child.setParents(parentList);
+            }
+            featureVectorDatasets.addAll(children);
+        }
+
+        featureVectorDatasets =
+                ScreeningUtils.filterExternalDataByType(featureVectorDatasets,
+                        featureVectorDatasetTypeCode);
+    }
+
+    private List<FeatureVectorDatasetReference> asFeatureVectorDatasets()
+    {
+        List<FeatureVectorDatasetReference> result = new ArrayList<FeatureVectorDatasetReference>();
+        for (ExternalData externalData : featureVectorDatasets)
+        {
+            result.add(asFeatureVectorDataset(externalData));
+        }
+        return result;
+    }
+
+    protected FeatureVectorDatasetReference asFeatureVectorDataset(ExternalData externalData)
+    {
+        DataStore dataStore = externalData.getDataStore();
+        ExternalData parentDataset = externalData.getParents().iterator().next();
+        return new FeatureVectorDatasetReference(externalData.getCode(),
+                dataStore.getDownloadUrl(), createPlateIdentifier(parentDataset),
+                extractPlateGeometry(parentDataset), externalData.getRegistrationDate(),
+                asImageDataset(parentDataset));
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ImageDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ImageDatasetLoader.java
new file mode 100644
index 00000000000..2dee96c43c8
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ImageDatasetLoader.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.plugin.screening.server.logic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataStore;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+import ch.systemsx.cisd.openbis.plugin.screening.server.IScreeningBusinessObjectFactory;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageDatasetReference;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateIdentifier;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+
+/**
+ * Utility class for loading image datasets.
+ * 
+ * @author Chandrasekhar Ramakrishnan
+ */
+class ImageDatasetLoader extends PlateDatasetLoader
+{
+    // TODO 2010-05-27, CR : See PlateDatasetLoader todo comment
+
+    ImageDatasetLoader(Session session, IScreeningBusinessObjectFactory businessObjectFactory,
+            String dataStoreBaseURL, List<? extends PlateIdentifier> plates)
+    {
+        super(session, businessObjectFactory, dataStoreBaseURL, plates,
+                ScreeningConstants.IMAGE_DATASET_TYPE);
+    }
+
+    /**
+     * Return the image datasets for the specified plates.
+     */
+    public List<ImageDatasetReference> getImageDatasets()
+    {
+        load();
+        return asImageDatasets();
+    }
+
+    private List<ImageDatasetReference> asImageDatasets()
+    {
+        List<ImageDatasetReference> result = new ArrayList<ImageDatasetReference>();
+        for (ExternalData externalData : getDatasets())
+        {
+            result.add(asImageDataset(externalData));
+        }
+        return result;
+    }
+
+    protected ImageDatasetReference asImageDataset(ExternalData externalData)
+    {
+        DataStore dataStore = externalData.getDataStore();
+        return new ImageDatasetReference(externalData.getCode(), dataStore.getDownloadUrl(),
+                createPlateIdentifier(externalData), extractPlateGeometry(externalData),
+                externalData.getRegistrationDate());
+    }
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/PlateDatasetLoader.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/PlateDatasetLoader.java
new file mode 100644
index 00000000000..891e0309cf7
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/PlateDatasetLoader.java
@@ -0,0 +1,258 @@
+package ch.systemsx.cisd.openbis.plugin.screening.server.logic;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import ch.systemsx.cisd.common.exceptions.UserFailureException;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.datasetlister.IDatasetLister;
+import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListOrSearchSampleCriteria;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
+import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleOwnerIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+import ch.systemsx.cisd.openbis.plugin.screening.server.IScreeningBusinessObjectFactory;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateIdentifier;
+import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
+
+/**
+ * A helper class for retrieving data sets assocaiated with plates.
+ * 
+ * @author Chandrasekhar Ramakrishnan
+ */
+class PlateDatasetLoader
+{
+    // TODO 2010-05-27, CR : This class and its subclasses should be refactored. The subclasses
+    // should become independent and use the dataset loader, instead of being dataset loaders.
+
+    // Infrastructure state
+    protected final Session session;
+
+    protected final IScreeningBusinessObjectFactory businessObjectFactory;
+
+    protected final String dataStoreBaseURL;
+
+    // Parameter state
+    private final List<? extends PlateIdentifier> plates;
+
+    private final String datasetTypeCode;
+
+    // Running state
+    private List<Sample> samples;
+
+    private List<ExternalData> datasets;
+
+    private HashMap<SampleIdentifier, Sample> samplesByIdentifier;
+
+    private HashMap<Long, Sample> samplesById;
+
+    PlateDatasetLoader(Session session, IScreeningBusinessObjectFactory businessObjectFactory,
+            String dataStoreBaseURL, List<? extends PlateIdentifier> plates, String datasetTypeCode)
+    {
+        this.session = session;
+        this.businessObjectFactory = businessObjectFactory;
+        this.dataStoreBaseURL = dataStoreBaseURL;
+        this.plates = plates;
+        this.datasetTypeCode = datasetTypeCode;
+    }
+
+    protected List<ExternalData> getDatasets()
+    {
+        return datasets;
+    }
+
+    /**
+     * Loads the samples and datasets. Call the method before calling {@link #getDatasets()}.
+     */
+    protected void load()
+    {
+        loadSamples();
+        loadDatasets();
+    }
+
+    private void loadSamples()
+    {
+        String[] sampleCodesArray = extractSampleCodes();
+        ListOrSearchSampleCriteria criteria = new ListOrSearchSampleCriteria(sampleCodesArray);
+        ISampleLister sampleLister = businessObjectFactory.createSampleLister(session);
+        samples = sampleLister.list(criteria);
+        // After loading the samples, we need to initialize the maps because other methods rely
+        // on this.
+        initializeSampleMaps();
+        filterSamplesByPlateIdentifiers();
+    }
+
+    private void loadDatasets()
+    {
+        List<Long> sampleIds = extractSampleIds();
+        IDatasetLister datasetLister =
+                businessObjectFactory.createDatasetLister(session, dataStoreBaseURL);
+        datasets = datasetLister.listBySampleIds(sampleIds);
+        datasets = ScreeningUtils.filterExternalDataByType(datasets, datasetTypeCode);
+    }
+
+    private void initializeSampleMaps()
+    {
+        samplesByIdentifier = new HashMap<SampleIdentifier, Sample>();
+        samplesById = new HashMap<Long, Sample>();
+        for (Sample sample : samples)
+        {
+            samplesByIdentifier.put(createSampleIdentifier(sample), sample);
+            samplesById.put(sample.getId(), sample);
+        }
+    }
+
+    /**
+     * Filter the list of samples, which were selected by code, to those that exactly match the
+     * identifiers in the plates
+     */
+    private void filterSamplesByPlateIdentifiers()
+    {
+        for (PlateIdentifier plate : plates)
+        {
+            Sample sample = samplesByIdentifier.get(createSampleIdentifier(plate));
+            // Make sure the sample and plate have the same *identifier* not just code
+            String plateSpaceCodeOrNull = plate.tryGetSpaceCode();
+            Space sampleSpaceOrNull = sample.getSpace();
+            String sampleSpaceCodeOrNull =
+                    (null == sampleSpaceOrNull) ? null : sampleSpaceOrNull.getCode();
+
+            // Remove the sample if they don't match
+            if (plateSpaceCodeOrNull == null)
+            {
+                if (sampleSpaceCodeOrNull != null)
+                {
+                    removeSample(sample);
+                }
+            } else if (false == plateSpaceCodeOrNull.equals(sampleSpaceCodeOrNull))
+            {
+                removeSample(sample);
+            }
+        }
+    }
+
+    private void removeSample(Sample sample)
+    {
+        samplesByIdentifier.remove(createSampleIdentifier(sample));
+        samplesById.remove(sample.getId());
+        samples.remove(sample);
+    }
+
+    protected PlateIdentifier createPlateIdentifier(ExternalData parentDataset)
+    {
+        Sample sample = getSample(parentDataset);
+        final String plateCode = sample.getCode();
+        Space space = sample.getSpace();
+        final String spaceCodeOrNull = (space != null) ? space.getCode() : null;
+        return new PlateIdentifier(plateCode, spaceCodeOrNull);
+    }
+
+    protected Geometry extractPlateGeometry(ExternalData dataSet)
+    {
+        Sample sample = getSample(dataSet);
+        List<IEntityProperty> properties = sample.getProperties();
+        for (IEntityProperty property : properties)
+        {
+            PropertyType propertyType = property.getPropertyType();
+            if (propertyType.getCode().equals(ScreeningConstants.PLATE_GEOMETRY))
+            {
+                String code = property.getVocabularyTerm().getCode();
+                int lastIndexOfUnderscore = code.lastIndexOf('_');
+                int lastIndexOfX = code.lastIndexOf('X');
+                if (lastIndexOfUnderscore < 0 || lastIndexOfX < 0)
+                {
+                    throw new UserFailureException("Invalid property "
+                            + ScreeningConstants.PLATE_GEOMETRY + ": " + code);
+                }
+                try
+                {
+                    int width =
+                            Integer.parseInt(code
+                                    .substring(lastIndexOfUnderscore + 1, lastIndexOfX));
+                    int height = Integer.parseInt(code.substring(lastIndexOfX + 1));
+                    return new Geometry(width, height);
+                } catch (NumberFormatException ex)
+                {
+                    throw new UserFailureException("Invalid property "
+                            + ScreeningConstants.PLATE_GEOMETRY + ": " + code);
+                }
+
+            }
+        }
+        throw new UserFailureException("Sample " + sample.getIdentifier() + " has no property "
+                + ScreeningConstants.PLATE_GEOMETRY);
+    }
+
+    private Sample getSample(ExternalData dataset)
+    {
+        // Sample may be NULL even though the selector does not begin with try
+        Sample sample = dataset.getSample();
+        assert sample != null : "dataset not connected to a sample: " + dataset;
+
+        // The dataset's reference to the sample is not complete, get the one from the map
+        return samplesById.get(sample.getId());
+    }
+
+    private String[] extractSampleCodes()
+    {
+        ArrayList<String> sampleCodes = new ArrayList<String>();
+        for (PlateIdentifier plate : plates)
+        {
+            sampleCodes.add(plate.getPlateCode());
+        }
+
+        String[] sampleCodesArray = new String[sampleCodes.size()];
+        sampleCodes.toArray(sampleCodesArray);
+
+        return sampleCodesArray;
+    }
+
+    private List<Long> extractSampleIds()
+    {
+        ArrayList<Long> sampleIds = new ArrayList<Long>();
+        for (Sample sample : samples)
+        {
+            sampleIds.add(sample.getId());
+        }
+        return sampleIds;
+    }
+
+    protected static SampleIdentifier createSampleIdentifier(PlateIdentifier plate)
+    {
+        SampleOwnerIdentifier owner;
+        String spaceCode = plate.tryGetSpaceCode();
+        if (spaceCode != null)
+        {
+            SpaceIdentifier space = new SpaceIdentifier(DatabaseInstanceIdentifier.HOME, spaceCode);
+            owner = new SampleOwnerIdentifier(space);
+        } else
+        {
+            owner = new SampleOwnerIdentifier(DatabaseInstanceIdentifier.createHome());
+        }
+        return SampleIdentifier.createOwnedBy(owner, plate.getPlateCode());
+    }
+
+    protected static SampleIdentifier createSampleIdentifier(Sample sample)
+    {
+        SampleOwnerIdentifier owner;
+        Space spaceOrNull = sample.getSpace();
+        String spaceCode = (null == spaceOrNull) ? null : spaceOrNull.getCode();
+        if (spaceCode != null)
+        {
+            SpaceIdentifier space = new SpaceIdentifier(DatabaseInstanceIdentifier.HOME, spaceCode);
+            owner = new SampleOwnerIdentifier(space);
+        } else
+        {
+            owner = new SampleOwnerIdentifier(DatabaseInstanceIdentifier.createHome());
+        }
+        return SampleIdentifier.createOwnedBy(owner, sample.getCode());
+    }
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java
index b52e5304008..f5077638af0 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImpl.java
@@ -17,13 +17,10 @@
 package ch.systemsx.cisd.openbis.plugin.screening.server.logic;
 
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import ch.systemsx.cisd.common.exceptions.UserFailureException;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.IExternalDataBO;
-import ch.systemsx.cisd.openbis.generic.server.business.bo.ISampleBO;
 import ch.systemsx.cisd.openbis.generic.server.business.bo.samplelister.ISampleLister;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.IDAOFactory;
 import ch.systemsx.cisd.openbis.generic.server.dataaccess.ISampleTypeDAO;
@@ -34,25 +31,13 @@ import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.SampleType;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Space;
-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.ExternalDataPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.GroupPE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.PropertyTypePE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePE;
-import ch.systemsx.cisd.openbis.generic.shared.dto.SamplePropertyPE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SampleTypePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.Session;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.DatabaseInstanceIdentifier;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleOwnerIdentifier;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.translator.SampleTypeTranslator;
 import ch.systemsx.cisd.openbis.plugin.screening.server.IScreeningBusinessObjectFactory;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.DatasetIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.FeatureVectorDatasetReference;
-import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Geometry;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.IDatasetIdentifier;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.ImageDatasetReference;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.Plate;
@@ -72,211 +57,32 @@ public class ScreeningApiImpl
 
     private final IDAOFactory daoFactory;
 
+    private final String dataStoreBaseURL;
+
     public ScreeningApiImpl(Session session, IScreeningBusinessObjectFactory businessObjectFactory,
-            IDAOFactory daoFactory)
+            IDAOFactory daoFactory, String dataStoreBaseURL)
     {
         this.session = session;
         this.businessObjectFactory = businessObjectFactory;
         this.daoFactory = daoFactory;
+        this.dataStoreBaseURL = dataStoreBaseURL;
     }
 
     public List<FeatureVectorDatasetReference> listFeatureVectorDatasets(
             List<? extends PlateIdentifier> plates)
     {
-        List<FeatureVectorDatasetReference> result = new ArrayList<FeatureVectorDatasetReference>();
-        List<ImageDatasetReference> imageDatasets = listImageDatasets(plates);
-        ISampleBO sampleBO = businessObjectFactory.createSampleBO(session);
-        Set<ExperimentPE> visitedExperiments = new HashSet<ExperimentPE>();
-        for (PlateIdentifier plate : plates)
-        {
-            ExperimentPE experiment = tryGetExperiment(sampleBO, plate);
-            if (experiment != null && visitedExperiments.contains(experiment) == false)
-            {
-                List<ExternalDataPE> datasets =
-                        daoFactory.getExternalDataDAO().listExternalData(experiment);
-                List<ExternalDataPE> childrenDatasets = filterChildren(imageDatasets, datasets);
-                List<ExternalDataPE> featureVectorDatasets =
-                        ScreeningUtils.filterDatasetsByType(childrenDatasets,
-                                ScreeningConstants.IMAGE_ANALYSIS_DATASET_TYPE);
-                result.addAll(asFeatureVectorDatasets(featureVectorDatasets));
-                visitedExperiments.add(experiment);
-            }
-        }
-        return result;
-    }
+        FeatureVectorDatasetLoader datasetRetriever =
+                new FeatureVectorDatasetLoader(session, businessObjectFactory, dataStoreBaseURL,
+                        plates);
+        List<FeatureVectorDatasetReference> result = datasetRetriever.getFeatureVectorDatasets();
 
-    // return those datasets which have exactly one parent, which is contained in the parent set
-    private static List<ExternalDataPE> filterChildren(
-            List<? extends IDatasetIdentifier> parentDatasets, List<ExternalDataPE> datasets)
-    {
-        Set<String> parentDatasetCodes = createDatasetCodesSet(parentDatasets);
-        List<ExternalDataPE> children = new ArrayList<ExternalDataPE>();
-        for (ExternalDataPE dataset : datasets)
-        {
-            Set<DataPE> parents = dataset.getParents();
-            if (parents.size() == 1)
-            {
-                DataPE parent = parents.iterator().next();
-                if (parentDatasetCodes.contains(parent.getCode()))
-                {
-                    children.add(dataset);
-                }
-            }
-        }
-        return children;
-    }
-
-    private static Set<String> createDatasetCodesSet(List<? extends IDatasetIdentifier> datasets)
-    {
-        Set<String> result = new HashSet<String>();
-        for (IDatasetIdentifier dataset : datasets)
-        {
-            result.add(dataset.getDatasetCode());
-        }
         return result;
     }
 
-    private ExperimentPE tryGetExperiment(ISampleBO sampleBO, PlateIdentifier plate)
-    {
-        sampleBO.loadBySampleIdentifier(createSampleIdentifier(plate));
-        SamplePE sample = sampleBO.getSample();
-        return sample.getExperiment();
-    }
-
     public List<ImageDatasetReference> listImageDatasets(List<? extends PlateIdentifier> plates)
     {
-        List<ExternalDataPE> datasets = loadDatasets(plates, ScreeningConstants.IMAGE_DATASET_TYPE);
-        return asImageDatasets(datasets);
-    }
-
-    // NOTE: this method is slow when a number of plates is big
-    private List<ExternalDataPE> loadDatasets(List<? extends PlateIdentifier> plates,
-            String datasetTypeCode)
-    {
-        ISampleBO sampleBO = businessObjectFactory.createSampleBO(session);
-        List<ExternalDataPE> datasets = new ArrayList<ExternalDataPE>();
-        for (PlateIdentifier plate : plates)
-        {
-            List<ExternalDataPE> plateDatasets = loadDatasets(plate, datasetTypeCode, sampleBO);
-            datasets.addAll(plateDatasets);
-        }
-        return datasets;
-    }
-
-    private List<ExternalDataPE> loadDatasets(PlateIdentifier plate, String datasetTypeCode,
-            ISampleBO sampleBO)
-    {
-        sampleBO.loadBySampleIdentifier(createSampleIdentifier(plate));
-        SamplePE sample = sampleBO.getSample();
-        List<ExternalDataPE> datasets = daoFactory.getExternalDataDAO().listExternalData(sample);
-        datasets = ScreeningUtils.filterDatasetsByType(datasets, datasetTypeCode);
-        return datasets;
-    }
-
-    private static List<FeatureVectorDatasetReference> asFeatureVectorDatasets(
-            List<ExternalDataPE> datasets)
-    {
-        List<FeatureVectorDatasetReference> result = new ArrayList<FeatureVectorDatasetReference>();
-        for (ExternalDataPE externalData : datasets)
-        {
-            result.add(asFeatureVectorDataset(externalData));
-        }
-        return result;
-    }
-
-    private static FeatureVectorDatasetReference asFeatureVectorDataset(ExternalDataPE externalData)
-    {
-        DataStorePE dataStore = externalData.getDataStore();
-        DataPE parentDataset = externalData.getParents().iterator().next();
-        return new FeatureVectorDatasetReference(externalData.getCode(),
-                dataStore.getDownloadUrl(), createPlateIdentifier(parentDataset),
-                extractPlateGeometry(parentDataset), externalData.getRegistrationDate(),
-                asImageDataset(parentDataset));
-    }
-
-    private static List<ImageDatasetReference> asImageDatasets(List<ExternalDataPE> datasets)
-    {
-        List<ImageDatasetReference> result = new ArrayList<ImageDatasetReference>();
-        for (ExternalDataPE externalData : datasets)
-        {
-            result.add(asImageDataset(externalData));
-        }
-        return result;
-    }
-
-    private static ImageDatasetReference asImageDataset(DataPE parentDataset)
-    {
-        DataStorePE dataStore = parentDataset.getDataStore();
-        return new ImageDatasetReference(parentDataset.getCode(), dataStore.getDownloadUrl(),
-                createPlateIdentifier(parentDataset), extractPlateGeometry(parentDataset),
-                parentDataset.getRegistrationDate());
-    }
-
-    private static PlateIdentifier createPlateIdentifier(DataPE parentDataset)
-    {
-        SamplePE sample = getSample(parentDataset);
-        final String plateCode = sample.getCode();
-        GroupPE group = sample.getGroup();
-        final String spaceCodeOrNull = (group != null) ? group.getCode() : null;
-        return new PlateIdentifier(plateCode, spaceCodeOrNull);
-    }
-
-    private static SamplePE getSample(DataPE dataset)
-    {
-        SamplePE sample = dataset.tryGetSample();
-        assert sample != null : "dataset not connected to a sample: " + dataset;
-        return sample;
-    }
-
-    private static Geometry extractPlateGeometry(DataPE dataSet)
-    {
-        SamplePE sample = getSample(dataSet);
-        Set<SamplePropertyPE> properties = sample.getProperties();
-        for (SamplePropertyPE property : properties)
-        {
-            PropertyTypePE propertyType = property.getEntityTypePropertyType().getPropertyType();
-            if (propertyType.getCode().equals(ScreeningConstants.PLATE_GEOMETRY))
-            {
-                String code = property.getVocabularyTerm().getCode();
-                int lastIndexOfUnderscore = code.lastIndexOf('_');
-                int lastIndexOfX = code.lastIndexOf('X');
-                if (lastIndexOfUnderscore < 0 || lastIndexOfX < 0)
-                {
-                    throw new UserFailureException("Invalid property "
-                            + ScreeningConstants.PLATE_GEOMETRY + ": " + code);
-                }
-                try
-                {
-                    int width =
-                            Integer.parseInt(code
-                                    .substring(lastIndexOfUnderscore + 1, lastIndexOfX));
-                    int height = Integer.parseInt(code.substring(lastIndexOfX + 1));
-                    return new Geometry(width, height);
-                } catch (NumberFormatException ex)
-                {
-                    throw new UserFailureException("Invalid property "
-                            + ScreeningConstants.PLATE_GEOMETRY + ": " + code);
-                }
-
-            }
-        }
-        throw new UserFailureException("Sample " + sample.getIdentifier() + " has no property "
-                + ScreeningConstants.PLATE_GEOMETRY);
-    }
-
-    private static SampleIdentifier createSampleIdentifier(PlateIdentifier plate)
-    {
-        SampleOwnerIdentifier owner;
-        String spaceCode = plate.tryGetSpaceCode();
-        if (spaceCode != null)
-        {
-            SpaceIdentifier space = new SpaceIdentifier(DatabaseInstanceIdentifier.HOME, spaceCode);
-            owner = new SampleOwnerIdentifier(space);
-        } else
-        {
-            owner = new SampleOwnerIdentifier(DatabaseInstanceIdentifier.createHome());
-        }
-        return SampleIdentifier.createOwnedBy(owner, plate.getPlateCode());
+        return new ImageDatasetLoader(session, businessObjectFactory, dataStoreBaseURL, plates)
+                .getImageDatasets();
     }
 
     public List<Plate> listPlates()
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningUtils.java b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningUtils.java
index 9b3725c9f8d..62ac9b54693 100644
--- a/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningUtils.java
+++ b/screening/source/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningUtils.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import ch.systemsx.cisd.bds.hcs.Location;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DataStorePE;
 import ch.systemsx.cisd.openbis.generic.shared.dto.ExternalDataPE;
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.DatasetReference;
@@ -81,4 +82,23 @@ public class ScreeningUtils
         return dataset.getDataSetType().getCode().equals(datasetType);
     }
 
+    public static List<ExternalData> filterExternalDataByType(List<ExternalData> datasets,
+            String datasetTypeCode)
+    {
+        List<ExternalData> chosenDatasets = new ArrayList<ExternalData>();
+        for (ExternalData dataset : datasets)
+        {
+            if (isTypeEqual(dataset, datasetTypeCode))
+            {
+                chosenDatasets.add(dataset);
+            }
+        }
+        return chosenDatasets;
+    }
+
+    private static boolean isTypeEqual(ExternalData dataset, String datasetType)
+    {
+        return dataset.getDataSetType().getCode().equals(datasetType);
+    }
+
 }
diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImplTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImplTest.java
index 0a648d3d203..43a2a1ffd2f 100644
--- a/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImplTest.java
+++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/plugin/screening/server/logic/ScreeningApiImplTest.java
@@ -44,23 +44,23 @@ import ch.systemsx.cisd.openbis.plugin.screening.shared.api.v1.dto.PlateIdentifi
 import ch.systemsx.cisd.openbis.plugin.screening.shared.basic.dto.ScreeningConstants;
 
 /**
- * 
- *
  * @author Franz-Josef Elmer
  */
 public class ScreeningApiImplTest extends AbstractServerTestCase
 {
     private static final String SERVER_URL = "server-url";
+
     private IScreeningBusinessObjectFactory screeningBOFactory;
+
     private ScreeningApiImpl screeningApi;
 
     @BeforeMethod
     public void beforeMethod()
     {
         screeningBOFactory = context.mock(IScreeningBusinessObjectFactory.class);
-        screeningApi = new ScreeningApiImpl(SESSION, screeningBOFactory, daoFactory);
+        screeningApi = new ScreeningApiImpl(SESSION, screeningBOFactory, daoFactory, "");
     }
-    
+
     @Test
     public void testListImageDatasets()
     {
@@ -82,8 +82,8 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
                 }
             });
 
-   List<ImageDatasetReference> dataSets = screeningApi.listImageDatasets(Arrays.asList(pi1));
-        
+        List<ImageDatasetReference> dataSets = screeningApi.listImageDatasets(Arrays.asList(pi1));
+
         assertEquals("1", dataSets.get(0).getDatasetCode());
         assertEquals(new Geometry(16, 24), dataSets.get(0).getPlateGeometry());
         assertEquals(new Date(100), dataSets.get(0).getRegistrationDate());
@@ -92,7 +92,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         assertEquals(1, dataSets.size());
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testListImageDatasetsWithMissingPlateGeometry()
     {
@@ -124,7 +124,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         }
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testListImageDatasetsWithPlateGeometryWithMissingUnderscore()
     {
@@ -138,40 +138,40 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         assertListImageDatasetsFailsFor("abc_2.4");
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testListImageDatasetsWithPlateGeometryWithWidthNotANumber()
     {
         assertListImageDatasetsFailsFor("abc_aX4");
         context.assertIsSatisfied();
     }
-    
+
     @Test
     public void testListImageDatasetsWithPlateGeometryWithHeightNotANumber()
     {
         assertListImageDatasetsFailsFor("abc_2Xb");
         context.assertIsSatisfied();
     }
-    
+
     private void assertListImageDatasetsFailsFor(final String plateGeometry)
     {
         final PlateIdentifier pi1 = new PlateIdentifier("p1", null);
         context.checking(new Expectations()
-        {
             {
-                one(screeningBOFactory).createSampleBO(SESSION);
-                will(returnValue(sampleBO));
-                
-                one(sampleBO).loadBySampleIdentifier(asSampleIdentifier(pi1));
-                one(sampleBO).getSample();
-                SamplePE p1 = plate(pi1, plateGeometry);
-                will(returnValue(p1));
-                
-                one(externalDataDAO).listExternalData(p1);
-                will(returnValue(Arrays.asList(imageDataSet(p1, "1"))));
-            }
-        });
-        
+                {
+                    one(screeningBOFactory).createSampleBO(SESSION);
+                    will(returnValue(sampleBO));
+
+                    one(sampleBO).loadBySampleIdentifier(asSampleIdentifier(pi1));
+                    one(sampleBO).getSample();
+                    SamplePE p1 = plate(pi1, plateGeometry);
+                    will(returnValue(p1));
+
+                    one(externalDataDAO).listExternalData(p1);
+                    will(returnValue(Arrays.asList(imageDataSet(p1, "1"))));
+                }
+            });
+
         try
         {
             screeningApi.listImageDatasets(Arrays.asList(pi1));
@@ -182,7 +182,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
                     + plateGeometry.toUpperCase(), ex.getMessage());
         }
     }
-    
+
     private SampleIdentifier asSampleIdentifier(PlateIdentifier plateIdentifier)
     {
         String spaceCode = plateIdentifier.tryGetSpaceCode();
@@ -194,7 +194,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         return new SampleIdentifier(new SpaceIdentifier(DatabaseInstanceIdentifier.createHome(),
                 spaceCode), plateIdentifier.getPlateCode());
     }
-    
+
     private SamplePE plate(PlateIdentifier plateIdentifier, String plateGeometryOrNull)
     {
         SamplePE sample = new SamplePE();
@@ -228,7 +228,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         dataSet.setDataSetType(dataSetType(ScreeningConstants.IMAGE_ANALYSIS_DATASET_TYPE));
         return dataSet;
     }
-    
+
     private ExternalDataPE createDataSet(SamplePE sample, String code)
     {
         ExternalDataPE dataSet = new ExternalDataPE();
@@ -240,7 +240,7 @@ public class ScreeningApiImplTest extends AbstractServerTestCase
         dataSet.setRegistrationDate(new Date(Long.parseLong(code) * 100));
         return dataSet;
     }
-    
+
     private DataSetTypePE dataSetType(String code)
     {
         DataSetTypePE type = new DataSetTypePE();
-- 
GitLab