From e890d2bbdb3218e40270916c67d95d9a0dbcfbcd Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Tue, 8 Jun 2010 12:29:03 +0000
Subject: [PATCH] LMS-1568 BDS migration

SVN: 16337
---
 .../bdsmigration/BDSDataRemoverMigrator.java  |  68 ++++
 .../bdsmigration/BDSImagingDbUploader.java    | 324 ++++++++++++++++++
 .../BDSMigrationMaintananceTask.java          | 227 ++++++++++++
 .../dss/etl/bdsmigration/IBDSMigrator.java    |  31 ++
 .../OriginalDataRelocatorMigrator.java        |  54 +++
 .../ScreeningDatasetInfoExtractor.java        | 131 +++++++
 6 files changed, 835 insertions(+)
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSDataRemoverMigrator.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSImagingDbUploader.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSMigrationMaintananceTask.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/IBDSMigrator.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/OriginalDataRelocatorMigrator.java
 create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/ScreeningDatasetInfoExtractor.java

diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSDataRemoverMigrator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSDataRemoverMigrator.java
new file mode 100644
index 00000000000..46de3964491
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSDataRemoverMigrator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.filesystem.FileUtilities;
+
+/**
+ * Removes unnecessary BDS data as a part of the migration.
+ * 
+ * @author Tomasz Pylak
+ */
+class BDSDataRemoverMigrator implements IBDSMigrator
+{
+
+    public String getDescription()
+    {
+        return "removing unnecessary BDS data";
+    }
+
+    public boolean migrate(File dataset)
+    {
+        if (BDSMigrationMaintananceTask.tryGetOriginalDir(dataset) != null)
+        {
+            BDSMigrationMaintananceTask.logError(dataset, "original data has not been moved");
+            return false;
+        }
+        try
+        {
+            removeDir(dataset, BDSMigrationMaintananceTask.METADATA_DIR);
+            removeDir(dataset, BDSMigrationMaintananceTask.VERSION_DIR);
+            removeDir(dataset, BDSMigrationMaintananceTask.ANNOTATIONS_DIR);
+            removeDir(dataset, BDSMigrationMaintananceTask.DATA_DIR);
+        } catch (EnvironmentFailureException ex)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    private void removeDir(File dataset, String relativeDirPath) throws EnvironmentFailureException
+    {
+        File dir = new File(dataset, relativeDirPath);
+        boolean ok = FileUtilities.deleteRecursively(dir);
+        if (ok == false)
+        {
+            String errorMsg = "Cannot delete the directory: " + dir.getAbsolutePath();
+            BDSMigrationMaintananceTask.operationLog.error(errorMsg);
+            throw new EnvironmentFailureException(errorMsg);
+        }
+    }
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSImagingDbUploader.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSImagingDbUploader.java
new file mode 100644
index 00000000000..55c68414a91
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSImagingDbUploader.java
@@ -0,0 +1,324 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.DIR_SEP;
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.METADATA_DIR;
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.ORIGINAL_DIR;
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.asNum;
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.readLines;
+import static ch.systemsx.cisd.openbis.dss.etl.bdsmigration.BDSMigrationMaintananceTask.tryGetOriginalDir;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import net.lemnik.eodsql.QueryTool;
+
+import org.apache.commons.lang.StringUtils;
+
+import ch.systemsx.cisd.bds.hcs.Location;
+import ch.systemsx.cisd.openbis.dss.etl.AcquiredPlateImage;
+import ch.systemsx.cisd.openbis.dss.etl.HCSDatasetUploader;
+import ch.systemsx.cisd.openbis.dss.etl.HCSImageFileExtractionResult;
+import ch.systemsx.cisd.openbis.dss.etl.RelativeImagePath;
+import ch.systemsx.cisd.openbis.dss.etl.ScreeningContainerDatasetInfo;
+import ch.systemsx.cisd.openbis.dss.etl.HCSImageFileExtractionResult.Channel;
+import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingUploadDAO;
+import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
+
+/**
+ * Uploads data to the imaging database.
+ * 
+ * @author Tomasz Pylak
+ */
+class BDSImagingDbUploader
+{
+    private static IImagingUploadDAO createQuery(Properties properties)
+    {
+        DataSource dataSource = ServiceProvider.getDataSourceProvider().getDataSource(properties);
+        return QueryTool.getQuery(dataSource, IImagingUploadDAO.class);
+    }
+
+    public static IBDSMigrator createImagingDbUploaderMigrator(Properties properties,
+            final String[] channelNames)
+    {
+        final IImagingUploadDAO dao = createQuery(properties);
+        return new IBDSMigrator()
+            {
+                public String getDescription()
+                {
+                    return "uploading data to the imaging database";
+                }
+
+                public boolean migrate(File dataset)
+                {
+                    return BDSImagingDbUploader.migrateDataset(dataset, dao, channelNames);
+                }
+            };
+    }
+
+    private static boolean migrateDataset(File dataset, IImagingUploadDAO dao, String[] channelNames)
+    {
+        String originalDatasetDirName = tryGetOriginalDatasetDirName(dataset);
+        if (originalDatasetDirName == null)
+        {
+            return false;
+        }
+        return new BDSImagingDbUploader(dataset, dao, originalDatasetDirName, channelNames)
+                .migrate();
+    }
+
+    private final File dataset;
+
+    private final IImagingUploadDAO dao;
+
+    private final String originalDatasetDirName;
+
+    private final String[] channelNames;
+
+    private BDSImagingDbUploader(File dataset, IImagingUploadDAO dao,
+            String originalDatasetDirName, String[] channelNames)
+    {
+        this.dataset = dataset;
+        this.dao = dao;
+        this.originalDatasetDirName = originalDatasetDirName;
+        this.channelNames = channelNames;
+    }
+
+    private boolean migrate()
+    {
+        List<AcquiredPlateImage> images = tryExtractMappings();
+        if (images == null)
+        {
+            return false;
+        }
+
+        String relativeImagesDirectory = getRelativeImagesDirectory();
+
+        ScreeningContainerDatasetInfo info =
+                ScreeningDatasetInfoExtractor.tryCreateInfo(dataset, relativeImagesDirectory);
+        if (info == null)
+        {
+            return false;
+        }
+
+        Set<HCSImageFileExtractionResult.Channel> channels = extractChannels();
+
+        return storeInDatabase(images, info, channels);
+    }
+
+    private Set<Channel> extractChannels()
+    {
+        Set<Channel> channels = new HashSet<Channel>();
+        for (String channelName : channelNames)
+        {
+            channels.add(new Channel(channelName, null, null));
+        }
+        return channels;
+    }
+
+    private boolean storeInDatabase(List<AcquiredPlateImage> images,
+            ScreeningContainerDatasetInfo info, Set<HCSImageFileExtractionResult.Channel> channels)
+    {
+        try
+        {
+            HCSDatasetUploader.upload(dao, info, images, channels);
+        } catch (Exception ex)
+        {
+            logError("Uploading to the imaging db failed: " + ex.getMessage());
+            dao.rollback();
+            return false;
+        }
+        dao.commit();
+        return true;
+    }
+
+    private String getRelativeImagesDirectory()
+    {
+        return ORIGINAL_DIR + DIR_SEP + originalDatasetDirName;
+    }
+
+    private static String tryGetOriginalDatasetDirName(File dataset)
+    {
+        File originalDir = tryGetOriginalDir(dataset);
+        if (originalDir == null)
+        {
+            return null;
+        }
+        File[] files = originalDir.listFiles();
+        if (files.length != 1)
+        {
+            BDSMigrationMaintananceTask.logError(dataset, "Original directory '" + originalDir
+                    + "' should contain exactly one file, but contains " + files.length + ": "
+                    + files);
+            return null;
+        }
+        return files[0].getName();
+    }
+
+    private List<AcquiredPlateImage> tryExtractMappings()
+    {
+        File mappingFile = new File(dataset, METADATA_DIR + DIR_SEP + "standard_original_mapping");
+        if (mappingFile.isFile() == false)
+        {
+            logError("File '" + mappingFile + "' does not exist.");
+            return null;
+        }
+
+        try
+        {
+            List<String> lines = readLines(mappingFile);
+            return tryParseMappings(lines);
+        } catch (IOException ex)
+        {
+            logError("Error when reading mapping file '" + mappingFile + "': " + ex.getMessage());
+            return null;
+        }
+    }
+
+    private List<AcquiredPlateImage> tryParseMappings(List<String> lines)
+    {
+        List<AcquiredPlateImage> images = new ArrayList<AcquiredPlateImage>();
+        for (String line : lines)
+        {
+            AcquiredPlateImage mapping = tryParseMapping(line);
+            if (mapping != null)
+            {
+                images.add(mapping);
+            } else
+            {
+                return null;
+            }
+        }
+        return images;
+    }
+
+    private AcquiredPlateImage tryParseMapping(String line)
+    {
+        String[] tokens = StringUtils.split(line);
+        if (tokens.length != 3)
+        {
+            logError("Wrong number of tokens in the mapping line: " + line);
+            return null;
+        } else
+        {
+            try
+            {
+                return tryParseMappingLine(tokens[0], tokens[2]);
+            } catch (NumberFormatException ex)
+            {
+                logError("Incorrect format of mapping line: " + line + ". Cannot parse a number: "
+                        + ex.getMessage());
+                return null;
+            }
+        }
+    }
+
+    // Example of standardPath: channel2/row1/column4/row2_column2.tiff
+    private AcquiredPlateImage tryParseMappingLine(String standardPath, String originalPath)
+            throws NumberFormatException
+    {
+        String[] pathTokens = standardPath.split("/");
+        if (pathTokens.length != 4)
+        {
+            logError("Wrong number of tokens in standard path: " + standardPath);
+            return null;
+        }
+        int channelNum = asNum(pathTokens[0], "channel");
+        int row = asNum(pathTokens[1], "row");
+        int col = asNum(pathTokens[2], "column");
+
+        String[] tileTokens = tryParseTileToken(pathTokens[3]);
+        if (tileTokens == null)
+        {
+            return null;
+        }
+        int tileRow = asNum(tileTokens[0], "row");
+        int tileCol = asNum(tileTokens[1], "column");
+
+        RelativeImagePath relativeImagePath = tryGetRelativeImagePath(originalPath);
+        if (relativeImagePath == null)
+        {
+            return null;
+        }
+        String channelName = tryGetChannelName(channelNum, standardPath);
+        if (channelName == null)
+        {
+            return null;
+        }
+        return new AcquiredPlateImage(new Location(col, row), new Location(tileCol, tileRow),
+                channelName, null, null, relativeImagePath);
+    }
+
+    // channelId - starts with 1
+    private String tryGetChannelName(int channelId, String standardPath)
+    {
+        if (channelNames.length < channelId)
+        {
+            logError("Name of the channel with the id " + channelId
+                    + " has not been configured but is referenced in the path: " + standardPath
+                    + ".");
+            return null;
+        }
+        return channelNames[channelId - 1];
+    }
+
+    private RelativeImagePath tryGetRelativeImagePath(String originalPath)
+    {
+        if (originalPath.startsWith(originalDatasetDirName) == false)
+        {
+            logError("Original path " + originalPath + " should start with "
+                    + originalDatasetDirName);
+            return null;
+        }
+        String relativePath = originalPath.substring(originalPath.length());
+        return new RelativeImagePath(relativePath);
+    }
+
+    // tileFile - e.g. row2_column2.tiff
+    private String[] tryParseTileToken(String tileFile)
+    {
+        String tileDesc;
+        int dotIndex = tileFile.indexOf(".");
+        if (dotIndex != -1)
+        {
+            tileDesc = tileFile.substring(0, dotIndex);
+        } else
+        {
+            tileDesc = tileFile;
+        }
+        String[] tileTokens = tileDesc.split("_");
+        if (tileTokens.length != 2)
+        {
+            logError("Wrong number of tokens in tile file name: " + tileDesc);
+            return null;
+        }
+        return tileTokens;
+    }
+
+    private void logError(String reason)
+    {
+        BDSMigrationMaintananceTask.logError(dataset, reason);
+    }
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSMigrationMaintananceTask.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSMigrationMaintananceTask.java
new file mode 100644
index 00000000000..5a368f6cd08
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/BDSMigrationMaintananceTask.java
@@ -0,0 +1,227 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.etlserver.IMaintenanceTask;
+import ch.systemsx.cisd.openbis.dss.etl.PlateStorageProcessor;
+
+/**
+ * Maintenance task which migrates all the BDS datasets to the imaging database.
+ * 
+ * @author Tomasz Pylak
+ */
+public class BDSMigrationMaintananceTask implements IMaintenanceTask
+{
+    static final Logger operationLog =
+            LogFactory.getLogger(LogCategory.OPERATION, BDSMigrationMaintananceTask.class);
+
+    static final String ANNOTATIONS_DIR = "annotations";
+
+    static final String METADATA_DIR = "metadata";
+
+    static final String DATA_DIR = "data";
+
+    static final String VERSION_DIR = "version";
+
+    static final String ORIGINAL_DIR = "original";
+
+    private static final String STORE_ROOT_PROPERTY = "storeRoot";
+
+    static final String DIR_SEP = "/";
+
+    private File storeRoot;
+
+    private String[] channelNames;
+
+    private Properties properties;
+
+    public void setUp(String pluginName, Properties properties)
+    {
+        String storeRootPath = properties.getProperty(STORE_ROOT_PROPERTY);
+        if (storeRootPath == null)
+        {
+            throw new EnvironmentFailureException(STORE_ROOT_PROPERTY + " property not specified.");
+        }
+        this.storeRoot = new File(storeRootPath);
+        if (storeRoot.isDirectory() == false)
+        {
+            throw new EnvironmentFailureException(storeRoot
+                    + " does not exist or is not a directory.");
+        }
+        this.channelNames = PlateStorageProcessor.extractChannelNames(properties);
+        this.properties = properties;
+    }
+
+    public void execute()
+    {
+        IBDSMigrator imagingDbUploader =
+                BDSImagingDbUploader.createImagingDbUploaderMigrator(properties, channelNames);
+        IBDSMigrator[] migrators =
+                new IBDSMigrator[]
+                    { imagingDbUploader, new OriginalDataRelocatorMigrator(),
+                            new BDSDataRemoverMigrator() };
+        for (IBDSMigrator migrator : migrators)
+        {
+            boolean ok = migrateStore(migrator);
+            if (ok == false)
+            {
+                operationLog.error("Migration stopped at: " + migrator.getDescription());
+                return;
+            }
+        }
+    }
+
+    private boolean migrateStore(IBDSMigrator migrator)
+    {
+        File[] files = storeRoot.listFiles();
+        for (File file : files)
+        {
+            String name = file.getName();
+            if (name.equals("error") == false && name.equals("unidentified") == false)
+            {
+                boolean ok = migrateDatabaseInstance(file, migrator);
+                if (ok == false)
+                {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean migrateDatabaseInstance(File dbInstanceDir, IBDSMigrator migrator)
+    {
+        int successCounter = 0, failureCounter = 0;
+        for (File l1 : dbInstanceDir.listFiles())
+        {
+            for (File l2 : l1.listFiles())
+            {
+                for (File l3 : l2.listFiles())
+                {
+                    for (File dataset : l3.listFiles())
+                    {
+                        boolean ok = migrateDataset(dataset, migrator);
+                        if (ok)
+                        {
+                            successCounter++;
+                        } else
+                        {
+                            failureCounter++;
+                        }
+                    }
+                }
+            }
+        }
+        logMigrationStats(migrator, successCounter, failureCounter);
+        return failureCounter == 0;
+    }
+
+    private void logMigrationStats(IBDSMigrator migrator, int successCounter, int failureCounter)
+    {
+        String desc = migrator.getDescription();
+        operationLog.info("Successful migration step '" + desc + "' of " + successCounter
+                + " datasets.");
+        operationLog.info("Unuccessful migration step '" + desc + "' of " + failureCounter
+                + " datasets.");
+    }
+
+    private boolean migrateDataset(File dataset, IBDSMigrator migrator)
+    {
+        if (isBDS(dataset))
+        {
+            boolean ok = migrator.migrate(dataset);
+            logMigrationFinished(ok, dataset, migrator.getDescription());
+            return ok;
+        } else
+        {
+            return true;
+        }
+    }
+
+    private static void logMigrationFinished(boolean ok, File dataset, String stepDescription)
+    {
+        String msg = "Migration step '" + stepDescription + "' of the dataset '" + dataset + "' ";
+        if (ok)
+        {
+            operationLog.info(msg + "succeeded.");
+        } else
+        {
+            operationLog.info(msg + "failed.");
+        }
+    }
+
+    static int asNum(String standardPathToken, String prefix) throws NumberFormatException
+    {
+        String number = standardPathToken.substring(prefix.length());
+        return Integer.parseInt(number);
+    }
+
+    @SuppressWarnings("unchecked")
+    static List<String> readLines(File mappingFile) throws IOException, FileNotFoundException
+    {
+        return IOUtils.readLines(new FileInputStream(mappingFile));
+    }
+
+    private static boolean isBDS(File dataset)
+    {
+        File[] files = dataset.listFiles();
+        return containsDir(files, VERSION_DIR) && containsDir(files, DATA_DIR)
+                && containsDir(files, METADATA_DIR) && containsDir(files, ANNOTATIONS_DIR);
+    }
+
+    private static boolean containsDir(File[] files, String dirName)
+    {
+        for (File file : files)
+        {
+            if (file.getName().equalsIgnoreCase(dirName))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static File tryGetOriginalDir(File dataset)
+    {
+        File orgDir = new File(dataset, DATA_DIR + DIR_SEP + ORIGINAL_DIR);
+        if (orgDir.isDirectory() == false)
+        {
+            logError(dataset, "Original directory does not exist: " + orgDir);
+            return null;
+        }
+        return orgDir;
+    }
+
+    static void logError(File dataset, String reason)
+    {
+        operationLog.error("Cannot migrate dataset '" + dataset.getName() + "'. " + reason);
+    }
+
+}
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/IBDSMigrator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/IBDSMigrator.java
new file mode 100644
index 00000000000..67c7442645e
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/IBDSMigrator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import java.io.File;
+
+/**
+ * @author Tomasz Pylak
+ */
+interface IBDSMigrator
+{
+    /** migrates one BDS dataset */
+    boolean migrate(File dataset);
+
+    /** user-friendly description of the migrator */
+    String getDescription();
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/OriginalDataRelocatorMigrator.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/OriginalDataRelocatorMigrator.java
new file mode 100644
index 00000000000..3d93bafbdfd
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/OriginalDataRelocatorMigrator.java
@@ -0,0 +1,54 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import java.io.File;
+
+/**
+ * Second step of BDS migration, moves data from data/original to original.
+ * 
+ * @author Tomasz Pylak
+ */
+class OriginalDataRelocatorMigrator implements IBDSMigrator
+{
+    public String getDescription()
+    {
+        return "moving data from data/original to original/";
+    }
+
+    public boolean migrate(File dataset)
+    {
+        File originalDir = BDSMigrationMaintananceTask.tryGetOriginalDir(dataset);
+        if (originalDir == null)
+        {
+            BDSMigrationMaintananceTask.operationLog.warn("No original data directory in dataset "
+                    + dataset);
+            return false;
+        }
+        File destinationDir = new File(dataset, BDSMigrationMaintananceTask.ORIGINAL_DIR);
+        boolean ok = originalDir.renameTo(destinationDir);
+        if (ok == false)
+        {
+            BDSMigrationMaintananceTask.operationLog.error("Cannot move " + originalDir + " to "
+                    + destinationDir);
+            return false;
+        } else
+        {
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/ScreeningDatasetInfoExtractor.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/ScreeningDatasetInfoExtractor.java
new file mode 100644
index 00000000000..df7ef3c9de2
--- /dev/null
+++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/bdsmigration/ScreeningDatasetInfoExtractor.java
@@ -0,0 +1,131 @@
+/*
+ * 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.dss.etl.bdsmigration;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.filesystem.FileOperations;
+import ch.systemsx.cisd.openbis.dss.etl.ScreeningContainerDatasetInfo;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
+import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SpaceIdentifier;
+
+/**
+ * Extract the dataset metadata from BDS and by asking openBIS. Used to migrate BDS to imaging db.
+ * 
+ * @author Tomasz Pylak
+ */
+class ScreeningDatasetInfoExtractor
+{
+    public static ScreeningContainerDatasetInfo tryCreateInfo(File dataset,
+            String relativeImagesDirectory)
+    {
+        Sample sample = tryGetSampleWithExperiment(dataset);
+        if (sample == null)
+        {
+            return null;
+        }
+        try
+        {
+            return createInfo(dataset, sample, relativeImagesDirectory);
+        } catch (Exception ex)
+        {
+            ex.printStackTrace();
+            BDSMigrationMaintananceTask.logError(dataset, "Unexpected exception: "
+                    + ex.getMessage());
+            return null;
+        }
+    }
+
+    private static Sample tryGetSampleWithExperiment(File dataset)
+    {
+        IEncapsulatedOpenBISService openBISService = ServiceProvider.getOpenBISService();
+        SampleIdentifier sampleIdentifier = createSampleIdentifier(dataset);
+        Sample sample = openBISService.tryGetSampleWithExperiment(sampleIdentifier);
+        if (sample == null)
+        {
+            BDSMigrationMaintananceTask.logError(dataset, "Sample '" + sampleIdentifier
+                    + "' cannot be found in openBIS");
+        }
+        return sample;
+    }
+
+    private static SampleIdentifier createSampleIdentifier(File dataset)
+    {
+        File sampleDir =
+                new File(dataset, BDSMigrationMaintananceTask.METADATA_DIR
+                        + BDSMigrationMaintananceTask.DIR_SEP + "sample");
+        String databaseInstanceCode = contentAsString(new File(sampleDir, "instance_code"));
+        String spaceCode = contentAsString(new File(sampleDir, "space_code"));
+        String sampleCode = contentAsString(new File(sampleDir, "code"));
+
+        SpaceIdentifier spaceIdentifier = new SpaceIdentifier(databaseInstanceCode, spaceCode);
+        return new SampleIdentifier(spaceIdentifier, sampleCode);
+    }
+
+    private static ScreeningContainerDatasetInfo createInfo(File dataset, Sample sample,
+            String relativeImagesDirectory)
+    {
+        int rows = extractGeometryDim(dataset, "plate_geometry", "rows");
+        int columns = extractGeometryDim(dataset, "plate_geometry", "columns");
+        int tileRows = extractGeometryDim(dataset, "well_geometry", "rows");
+        int tileColumns = extractGeometryDim(dataset, "well_geometry", "columns");
+
+        ScreeningContainerDatasetInfo info = new ScreeningContainerDatasetInfo();
+        info.setContainerRows(rows);
+        info.setContainerColumns(columns);
+        info.setTileRows(tileRows);
+        info.setTileColumns(tileColumns);
+
+        info.setRelativeImagesDirectory(relativeImagesDirectory);
+
+        info.setDatasetPermId(extractDatasetPermId(dataset));
+        info.setContainerPermId(sample.getPermId());
+        info.setExperimentPermId(sample.getExperiment().getPermId());
+
+        return info;
+    }
+
+    private static int extractGeometryDim(File dataset, String geometryName, String fieldName)
+    {
+        File parentDir =
+                new File(BDSMigrationMaintananceTask.METADATA_DIR, "parameters"
+                        + BDSMigrationMaintananceTask.DIR_SEP + geometryName);
+        return contentAsNumber(new File(parentDir, fieldName));
+    }
+
+    private static String extractDatasetPermId(File dataset)
+    {
+        File file =
+                new File(dataset, BDSMigrationMaintananceTask.METADATA_DIR
+                        + BDSMigrationMaintananceTask.DIR_SEP + "data_set"
+                        + BDSMigrationMaintananceTask.DIR_SEP + "code");
+        return contentAsString(file);
+    }
+
+    private static int contentAsNumber(File file)
+    {
+        return Integer.parseInt(contentAsString(file));
+    }
+
+    private static String contentAsString(File file)
+    {
+        return FileOperations.getInstance().getContentAsString(file);
+    }
+}
\ No newline at end of file
-- 
GitLab