From 061bab1bd8d24230177fa577bb640b4193e3f19f Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Tue, 4 Jun 2013 10:53:23 +0000
Subject: [PATCH] SP-668, BIS-428: handle checksum in case of uncompressed zip
 file. ZipDataSetpackagerTest added.

SVN: 29280
---
 .../server/AbstractDataSetPackager.java       |  38 ++-
 .../dss/generic/server/MetaDataBuilder.java   |  11 +-
 .../generic/server/ZipDataSetPackager.java    |  23 +-
 .../plugins/standard/DistributedArchiver.java | 164 ----------
 .../server/ZipDataSetPackagerTest.java        | 285 ++++++++++++++++++
 5 files changed, 346 insertions(+), 175 deletions(-)
 delete mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedArchiver.java
 create mode 100644 datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackagerTest.java

diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDataSetPackager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDataSetPackager.java
index 27fa640ee66..81700116234 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDataSetPackager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDataSetPackager.java
@@ -19,7 +19,9 @@ package ch.systemsx.cisd.openbis.dss.generic.server;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.util.List;
+import java.util.zip.CRC32;
 
+import ch.systemsx.cisd.common.io.IOUtilities;
 import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent;
 import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContentNode;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider;
@@ -49,8 +51,16 @@ public abstract class AbstractDataSetPackager
     /**
      * Adds an entry with specified entry path and last modification date filled with data from
      * specified input stream.
+     * 
+     * @param size Number of bytes.
+     * @param checksum Checksum. Can be 0 if {@link #isChecksumNeeded()} return <code>false</code>.
      */
-    public abstract void addEntry(String entryPath, long lastModified, InputStream in);
+    public abstract void addEntry(String entryPath, long lastModified, long size, long checksum, InputStream in);
+    
+    /**
+     * Returns <code>true</code> if the checksum is needed.
+     */
+    protected abstract boolean isChecksumNeeded();
     
     /**
      * Closes the package.
@@ -61,8 +71,15 @@ public abstract class AbstractDataSetPackager
     {
         try
         {
-            addEntry(rootPath + META_DATA_FILE_NAME, System.currentTimeMillis(),
-                    new ByteArrayInputStream(MetaDataBuilder.createMetaData(externalData).getBytes()));
+            byte[] bytes = MetaDataBuilder.createMetaData(externalData).getBytes();
+            CRC32 checksumCalculator = new CRC32();
+            for (byte b : bytes)
+            {
+                checksumCalculator.update(0xff & b);
+            }
+            Long checksum = checksumCalculator.getValue();
+            addEntry(rootPath + META_DATA_FILE_NAME, System.currentTimeMillis(), new Long(bytes.length), checksum,
+                    new ByteArrayInputStream(bytes));
         } catch (Exception ex)
         {
             throw new RuntimeException(
@@ -111,7 +128,20 @@ public abstract class AbstractDataSetPackager
             }
         } else
         {
-            addEntry(newRootPath + node.getRelativePath(), node.getLastModified(),
+            long size = node.getFileLength();
+            long checksum = 0;
+            if (isChecksumNeeded())
+            {
+                boolean checksumCRC32Precalculated = node.isChecksumCRC32Precalculated();
+                if (checksumCRC32Precalculated)
+                {
+                    checksum = node.getChecksumCRC32();
+                } else 
+                {
+                    checksum = IOUtilities.getChecksumCRC32(node.getInputStream());
+                }
+            }
+            addEntry(newRootPath + node.getRelativePath(), node.getLastModified(), size, checksum,
                     node.getInputStream());
         }
     }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
index adb91cd1ccb..04d2ebbde41 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/MetaDataBuilder.java
@@ -19,6 +19,7 @@ package ch.systemsx.cisd.openbis.dss.generic.server;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -86,10 +87,10 @@ public class MetaDataBuilder
         builder.dataSetProperties(dataSet.getProperties());
     
         StringBuilder stringBuilder = new StringBuilder();
-        List<AbstractExternalData> parents = new ArrayList<AbstractExternalData>(dataSet.getParents());
-        Collections.sort(parents, DATA_SET_COMPARATOR);
+        List<AbstractExternalData> parents = getParents(dataSet);
         if (parents.isEmpty() == false)
         {
+            Collections.sort(parents, DATA_SET_COMPARATOR);
             for (AbstractExternalData parent : parents)
             {
                 if (stringBuilder.length() > 0)
@@ -123,6 +124,12 @@ public class MetaDataBuilder
         return builder.getRenderedMetaData();
     }
 
+    private static List<AbstractExternalData> getParents(AbstractExternalData dataSet)
+    {
+        Collection<AbstractExternalData> parents = dataSet.getParents();
+        return parents == null ? new ArrayList<AbstractExternalData>() : new ArrayList<AbstractExternalData>(parents);
+    }
+
     private final StringBuilder builder = new StringBuilder();
 
     private void dataSetProperties(List<IEntityProperty> properties)
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackager.java
index a9088d310f0..5fff70928f5 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackager.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackager.java
@@ -51,29 +51,42 @@ public class ZipDataSetPackager extends AbstractDataSetPackager
     }
 
     @Override
-    public void addEntry(String entryPath, long lastModified, InputStream in)
+    protected boolean isChecksumNeeded()
     {
+        return compress == false;
+    }
+    
+    @Override
+    public void addEntry(String entryPath, long lastModified, long size, long checksum, InputStream in)
+    {
+        ZipOutputStream zos = getZipOutputStream();
         try
         {
             ZipEntry zipEntry = new ZipEntry(entryPath.replace('\\', '/'));
             zipEntry.setTime(lastModified);
             zipEntry.setMethod(compress ? ZipEntry.DEFLATED : ZipEntry.STORED);
-            getZipOutputStream().putNextEntry(zipEntry);
+            if (compress == false)
+            {
+                zipEntry.setSize(size);
+                zipEntry.setCompressedSize(size);
+                zipEntry.setCrc(0xffffffffL & checksum);
+            }
+            zos.putNextEntry(zipEntry);
             int len;
             byte[] buffer = new byte[1024];
             while ((len = in.read(buffer)) > 0)
             {
-                getZipOutputStream().write(buffer, 0, len);
+                zos.write(buffer, 0, len);
             }
         } catch (IOException ex)
         {
-            throw CheckedExceptionTunnel.wrapIfNecessary(ex);
+            throw new RuntimeException("Error while adding entry " + entryPath, ex);
         } finally
         {
             IOUtils.closeQuietly(in);
             try
             {
-                getZipOutputStream().closeEntry();
+                zos.closeEntry();
             } catch (IOException ex)
             {
                 throw CheckedExceptionTunnel.wrapIfNecessary(ex);
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedArchiver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedArchiver.java
deleted file mode 100644
index 55d3b57d228..00000000000
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedArchiver.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2013 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
-import ch.systemsx.cisd.common.exceptions.Status;
-import ch.systemsx.cisd.common.filesystem.BooleanStatus;
-import ch.systemsx.cisd.common.properties.PropertyUtils;
-import ch.systemsx.cisd.common.time.TimingParameters;
-import ch.systemsx.cisd.openbis.dss.generic.server.AbstractDataSetPackager;
-import ch.systemsx.cisd.openbis.dss.generic.server.ZipDataSetPackager;
-import ch.systemsx.cisd.openbis.dss.generic.shared.ArchiverTaskContext;
-import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider;
-import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
-import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider;
-import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
-import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DataSetExistenceChecker;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.AbstractExternalData;
-import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IDatasetLocation;
-import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
-import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory;
-
-/**
- * 
- *
- * @author Franz-Josef Elmer
- */
-public class DistributedArchiver extends AbstractArchiverProcessingPlugin
-{
-    private static final long serialVersionUID = 1L;
-    
-    private final boolean compress;
-    
-    private File destination;
-    
-    public DistributedArchiver(Properties properties, File storeRoot)
-    {
-        super(properties, storeRoot, null, null);
-        destination = new File(properties.getProperty("destination"));
-        compress = PropertyUtils.getBoolean(properties, "compress", true);
-        destination.mkdirs();
-    }
-    
-    @Override
-    protected DatasetProcessingStatuses doArchive(List<DatasetDescription> datasets, ArchiverTaskContext context)
-    {
-        List<AbstractExternalData> dataSets = getDataSetMetaData(datasets);
-        IHierarchicalContentProvider contentProvider = context.getHierarchicalContentProvider();
-        IDataSetDirectoryProvider directoryProvider = context.getDirectoryProvider();
-        IShareIdManager shareIdManager = directoryProvider.getShareIdManager();
-        DataSetExistenceChecker dataSetExistenceChecker =
-                new DataSetExistenceChecker(directoryProvider, TimingParameters.create(new Properties()));
-        DatasetProcessingStatuses statuses = new DatasetProcessingStatuses();
-        for (AbstractExternalData dataSet : dataSets)
-        {
-            Status status = Status.OK;
-            String dataSetCode = dataSet.getCode();
-            File file = new File(getArchive(dataSet), dataSetCode + ".zip");
-            shareIdManager.lock(dataSetCode);
-            AbstractDataSetPackager dataSetPackager = null;
-            try
-            {
-                dataSetPackager = createPackager(file, contentProvider, dataSetExistenceChecker);
-                dataSetPackager.addDataSetTo("", dataSet);
-            } catch (Exception ex)
-            {
-                status = Status.createError(ex.toString());
-                operationLog.error("Couldn't create package file: " + file, ex);
-            } finally
-            {
-                if (dataSetPackager != null)
-                {
-                    try
-                    {
-                        dataSetPackager.close();
-                    } catch (Exception ex)
-                    {
-                        status = Status.createError("Couldn't close package file: " + file + ": " + ex);
-                    }
-                }
-                shareIdManager.releaseLock(dataSetCode);
-                operationLog.info("Data set " + dataSetCode + " archived: " + file);
-            }
-            statuses.addResult(dataSetCode, status, Operation.ARCHIVE);
-        }
-        return statuses;
-    }
-
-    private AbstractDataSetPackager createPackager(File file, IHierarchicalContentProvider contentProvider,
-            DataSetExistenceChecker dataSetExistenceChecker)
-    {
-        return new ZipDataSetPackager(file, compress, contentProvider, dataSetExistenceChecker);
-    }
-
-    private List<AbstractExternalData> getDataSetMetaData(List<DatasetDescription> datasets)
-    {
-        IEncapsulatedOpenBISService service = getService();
-        List<AbstractExternalData> dataSets = new ArrayList<AbstractExternalData>();
-        for (DatasetDescription datasetDescription : datasets)
-        {
-            AbstractExternalData dataSet = service.tryGetDataSet(datasetDescription.getDataSetCode());
-            String experimentIdentifier = datasetDescription.getExperimentIdentifier();
-            dataSet.setExperiment(service.tryGetExperiment(ExperimentIdentifierFactory.parse(experimentIdentifier)));
-            String sampleIdentifier = datasetDescription.getSampleIdentifier();
-            if (sampleIdentifier != null)
-            {
-                dataSet.setSample(service.tryGetSampleWithExperiment(SampleIdentifierFactory.parse(sampleIdentifier)));
-            }
-            dataSets.add(dataSet);
-        }
-        return dataSets;
-    }
-    
-    private File getArchive(AbstractExternalData dataSet)
-    {
-        return destination;
-    }
-
-    @Override
-    protected DatasetProcessingStatuses doUnarchive(List<DatasetDescription> datasets, ArchiverTaskContext context)
-    {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
-    protected DatasetProcessingStatuses doDeleteFromArchive(List<? extends IDatasetLocation> datasets)
-    {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
-    protected BooleanStatus isDataSetSynchronizedWithArchive(DatasetDescription dataset, ArchiverTaskContext context)
-    {
-        return BooleanStatus.createFalse();
-    }
-
-    @Override
-    protected BooleanStatus isDataSetPresentInArchive(DatasetDescription dataset)
-    {
-        return BooleanStatus.createFalse();
-    }
-    
-}
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackagerTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackagerTest.java
new file mode 100644
index 00000000000..beb0763844a
--- /dev/null
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ZipDataSetPackagerTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2013 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.dss.generic.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.io.IOUtils;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase;
+import ch.systemsx.cisd.common.filesystem.FileUtilities;
+import ch.systemsx.cisd.common.io.IOUtilities;
+import ch.systemsx.cisd.common.server.ISessionTokenProvider;
+import ch.systemsx.cisd.common.spring.ExposablePropertyPlaceholderConfigurer;
+import ch.systemsx.cisd.common.time.TimingParameters;
+import ch.systemsx.cisd.openbis.common.io.hierarchical_content.DefaultFileBasedHierarchicalContentFactory;
+import ch.systemsx.cisd.openbis.common.io.hierarchical_content.IHierarchicalContentFactory;
+import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetDirectoryProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.HierarchicalContentProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IConfigProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
+import ch.systemsx.cisd.openbis.dss.generic.shared.content.DssServiceRpcGenericFactory;
+import ch.systemsx.cisd.openbis.dss.generic.shared.content.IContentCache;
+import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DataSetExistenceChecker;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DatasetLocationNode;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PhysicalDataSet;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataSetBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataStoreBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.ExperimentBuilder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.SampleBuilder;
+
+import de.schlichtherle.io.FileInputStream;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class ZipDataSetPackagerTest extends AbstractFileSystemTestCase
+{
+    private static final String DATA_SET_CODE = "ds1";
+    private static final String DSS_CODE = "DSS";
+    private static final String SHARE_ID = "1";
+    private Mockery context;
+    private IEncapsulatedOpenBISService openbisService;
+    private IShareIdManager shareIdManager;
+    private IConfigProvider configProvider;
+    private HierarchicalContentProvider contentProvider;
+    private DataSetExistenceChecker existenceChecker;
+    private IHierarchicalContentFactory contentProviderFactory;
+    private File rootFolder;
+
+    @BeforeMethod
+    public void prepareTestFixture()
+    {
+        context = new Mockery();
+        openbisService = context.mock(IEncapsulatedOpenBISService.class);
+        shareIdManager = context.mock(IShareIdManager.class);
+        configProvider = context.mock(IConfigProvider.class);
+        context.checking(new Expectations()
+            {
+                {
+                    allowing(configProvider).getStoreRoot();
+                    will(returnValue(workingDirectory));
+                    
+                    allowing(configProvider).getDataStoreCode();
+                    will(returnValue(DSS_CODE));
+                }
+            });
+        IContentCache contentCache = null;
+        ISessionTokenProvider sessionTokenProvider = null;
+        ExposablePropertyPlaceholderConfigurer infoProvider = null;
+        IDataSetDirectoryProvider dataSetDirectoryProvider = new DataSetDirectoryProvider(workingDirectory, shareIdManager);
+        contentProviderFactory = new DefaultFileBasedHierarchicalContentFactory();
+        contentProvider = new HierarchicalContentProvider(openbisService, dataSetDirectoryProvider, contentProviderFactory,
+                new DssServiceRpcGenericFactory(), contentCache, sessionTokenProvider, DSS_CODE, infoProvider);
+        existenceChecker = new DataSetExistenceChecker(dataSetDirectoryProvider, TimingParameters.getDefaultParameters());
+        File share = new File(workingDirectory, SHARE_ID);
+        share.mkdirs();
+        rootFolder = new File(share, DATA_SET_CODE);
+        File subFolder = new File(rootFolder, "subfolder");
+        subFolder.mkdirs();
+        FileUtilities.writeToFile(new File(rootFolder, "read.me"), "Don't read me!");
+        FileUtilities.writeToFile(new File(subFolder, "change-log.txt"), "nothing changed");
+    }
+    
+    @AfterMethod
+    public void tearDown()
+    {
+        // To following line of code should also be called at the end of each test method.
+        // Otherwise one does not known which test failed.
+        context.assertIsSatisfied();
+    }
+
+    @Test
+    public void testCreateCompressed() throws Exception
+    {
+        PhysicalDataSet dataSet = new DataSetBuilder().store(new DataStoreBuilder(DSS_CODE).getStore())
+                .code(DATA_SET_CODE).type("MY-DATASET").fileFormat("XML").location(DATA_SET_CODE).property("AGE", "42")
+                .experiment(new ExperimentBuilder().identifier("/S/P/E1").type("MY-EXPERIMENT").property("GREETINGS", "Hello").getExperiment())
+                .getDataSet();
+        prepareShareIdManager(DATA_SET_CODE);
+        prepareGetDataSetLocation(dataSet);
+        File zipFile = new File(workingDirectory, "archived1.zip");
+        ZipDataSetPackager packager = new ZipDataSetPackager(zipFile, true, contentProvider, existenceChecker);
+        
+        packager.addDataSetTo("", dataSet);
+        packager.close();
+        
+        checkZipFileContent(zipFile, dataSet, "data_set\tcode\tds1\n" + 
+                "data_set\tproduction_timestamp\t\n" + 
+                "data_set\tproducer_code\t\n" + 
+                "data_set\tdata_set_type\tMY-DATASET\n" + 
+                "data_set\tis_measured\tTRUE\n" + 
+                "data_set\tis_complete\tFALSE\n" + 
+                "data_set\tAGE\t42\n" + 
+                "data_set\tparent_codes\t\n" + 
+                "experiment\tspace_code\tS\n" + 
+                "experiment\tproject_code\tP\n" + 
+                "experiment\texperiment_code\tE1\n" + 
+                "experiment\texperiment_type_code\tMY-EXPERIMENT\n" + 
+                "experiment\tregistration_timestamp\t\n" + 
+                "experiment\tregistrator\t\n" + 
+                "experiment\tGREETINGS\tHello\n");
+        context.assertIsSatisfied();
+    }
+    
+    @Test
+    public void testCreateUncompressed() throws Exception
+    {
+        PhysicalDataSet dataSet = new DataSetBuilder().store(new DataStoreBuilder(DSS_CODE).getStore())
+                .code(DATA_SET_CODE).type("MY-DATASET").fileFormat("XML").location(DATA_SET_CODE).property("AGE", "24")
+                .experiment(new ExperimentBuilder().identifier("/S/P/E1").type("MY-EXPERIMENT").getExperiment())
+                .sample(new SampleBuilder().identifier("/S/S1").type("MY-SAMPLE").property("GREETINGS", "Hi").getSample())
+                .parent(new DataSetBuilder().code("P1").getDataSet())
+                .getDataSet();
+        prepareShareIdManager(DATA_SET_CODE);
+        prepareGetDataSetLocation(dataSet);
+        File zipFile = new File(workingDirectory, "archived2.zip");
+        ZipDataSetPackager packager = new ZipDataSetPackager(zipFile, false, contentProvider, existenceChecker);
+        
+        packager.addDataSetTo("", dataSet);
+        packager.close();
+        
+        checkZipFileContent(zipFile, dataSet, "data_set\tcode\tds1\n" +
+                "data_set\tproduction_timestamp\t\n" +
+                "data_set\tproducer_code\t\n" +
+                "data_set\tdata_set_type\tMY-DATASET\n" +
+                "data_set\tis_measured\tTRUE\n" +
+                "data_set\tis_complete\tFALSE\n" +
+                "data_set\tAGE\t24\n" +
+                "data_set\tparent_codes\tP1\n" +
+                "sample\ttype_code\tMY-SAMPLE\n" +
+                "sample\tcode\tS1\n" +
+                "sample\tspace_code\tS\n" +
+                "sample\tregistration_timestamp\t\n" +
+                "sample\tregistrator\t\n" +
+                "sample\tGREETINGS\tHi\n" +
+                "experiment\tspace_code\tS\n" +
+                "experiment\tproject_code\tP\n" +
+                "experiment\texperiment_code\tE1\n" +
+                "experiment\texperiment_type_code\tMY-EXPERIMENT\n" +
+                "experiment\tregistration_timestamp\t\n" +
+                "experiment\tregistrator\t\n");
+        context.assertIsSatisfied();
+    }
+
+    private void checkZipFileContent(File zipFile, PhysicalDataSet dataSet, String metaData) throws Exception
+    {
+        assertEquals(true, zipFile.exists());
+        ZipFile zFile = new ZipFile(zipFile);
+        for (ZipEntry zipEntry : Collections.list(zFile.entries()))
+        {
+            String relativePath = zipEntry.getName();
+            if (relativePath.equals(AbstractDataSetPackager.META_DATA_FILE_NAME))
+            {
+                checkContent(zFile, zipEntry, new ByteArrayInputStream(metaData.getBytes()));
+            } else
+            {
+                checkZipEntry(zFile, zipEntry);
+            }
+        }
+    }
+
+    private void checkZipEntry(ZipFile zf, ZipEntry entry) throws Exception
+    {
+        String relativePath = entry.getName();
+        File file = new File(rootFolder, relativePath);
+        assertEquals(relativePath + " exists", true, file.exists());
+        assertEquals(relativePath + " folder or file", file.isDirectory(), entry.isDirectory());
+        if (entry.isDirectory())
+        {
+            return;
+        }
+        assertEquals(relativePath + " size", file.length(), entry.getSize());
+        assertEquals(relativePath + " checksum", calculateChecksum(file), (int) entry.getCrc());
+        checkContent(zf, entry, new FileInputStream(file));
+    }
+
+    private void checkContent(ZipFile zf, ZipEntry entry, InputStream inputStream) throws Exception
+    {
+        String expectedContent = getContentAndClose(inputStream);
+        String actualContent = getContentAndClose(zf.getInputStream(entry));
+        assertEquals(entry.getName() + " content", expectedContent, actualContent);
+    }
+    
+    private String getContentAndClose(InputStream inputStream) throws Exception
+    {
+        try
+        {
+            ByteArrayOutputStream output = new ByteArrayOutputStream();
+            IOUtils.copy(inputStream, output);
+            return output.toString();
+        } finally
+        {
+            IOUtils.closeQuietly(inputStream);
+        }
+    }
+
+    private int calculateChecksum(File file) throws Exception
+    {
+        InputStream inputStream = null;
+        try
+        {
+            inputStream = new FileInputStream(file);
+            return IOUtilities.getChecksumCRC32(inputStream);
+        } finally
+        {
+            IOUtils.closeQuietly(inputStream);
+        }
+    }
+    
+    private void prepareGetDataSetLocation(final PhysicalDataSet dataSet)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    one(openbisService).tryGetDataSetLocation(dataSet.getCode());
+                    will(returnValue(new DatasetLocationNode(dataSet)));
+                }
+            });
+    }
+
+    private void prepareShareIdManager(final String dataSetCode)
+    {
+        context.checking(new Expectations()
+            {
+                {
+                    exactly(2).of(shareIdManager).getShareId(dataSetCode);
+                    will(returnValue(SHARE_ID));
+                    
+                    one(shareIdManager).lock(dataSetCode);
+                    
+                    one(shareIdManager).releaseLock(dataSetCode);
+                }
+            });
+    }
+
+}
-- 
GitLab