diff --git a/common/source/java/ch/systemsx/cisd/common/logging/Log4jSimpleLogger.java b/common/source/java/ch/systemsx/cisd/common/logging/Log4jSimpleLogger.java index 4466a655fd7952a499a889115b5ce1246b47bd73..a650f0f0d0f5348983c731f0e8a3872c1e4a8a41 100644 --- a/common/source/java/ch/systemsx/cisd/common/logging/Log4jSimpleLogger.java +++ b/common/source/java/ch/systemsx/cisd/common/logging/Log4jSimpleLogger.java @@ -92,10 +92,10 @@ public class Log4jSimpleLogger implements ISimpleLogger { if (log4jOverridePriorityOrNull != null) { - log4jLogger.log(log4jOverridePriorityOrNull, message); + log4jLogger.log(log4jOverridePriorityOrNull, message, throwableOrNull); } else { - log4jLogger.log(toLog4jPriority(level), message, null); + log4jLogger.log(toLog4jPriority(level), message, throwableOrNull); } } 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 db88e82edcea5c5e4438171519c9e3f5421ece7d..27fa640ee665ebbf65031ed906847bf500b6a1c4 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 @@ -20,8 +20,6 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.List; -import ch.systemsx.cisd.common.logging.ISimpleLogger; -import ch.systemsx.cisd.common.logging.LogLevel; 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; @@ -38,36 +36,41 @@ public abstract class AbstractDataSetPackager { public static final String META_DATA_FILE_NAME = "meta-data.tsv"; - private final ISimpleLogger logger; private final IHierarchicalContentProvider contentProvider; private final DataSetExistenceChecker dataSetExistenceChecker; - protected AbstractDataSetPackager(ISimpleLogger logger, IHierarchicalContentProvider contentProvider, + protected AbstractDataSetPackager(IHierarchicalContentProvider contentProvider, DataSetExistenceChecker dataSetExistenceChecker) { - this.logger = logger; this.contentProvider = contentProvider; this.dataSetExistenceChecker = dataSetExistenceChecker; } + + /** + * Adds an entry with specified entry path and last modification date filled with data from + * specified input stream. + */ + public abstract void addEntry(String entryPath, long lastModified, InputStream in); + + /** + * Closes the package. + */ + public abstract void close(); - public boolean addDataSetTo(String rootPath, AbstractExternalData externalData) + public void addDataSetTo(String rootPath, AbstractExternalData externalData) { try { - addEntry(rootPath + META_DATA_FILE_NAME, - System.currentTimeMillis(), + addEntry(rootPath + META_DATA_FILE_NAME, System.currentTimeMillis(), new ByteArrayInputStream(MetaDataBuilder.createMetaData(externalData).getBytes())); } catch (Exception ex) { - logger.log(LogLevel.ERROR, - "Couldn't add meta data for data set '" + externalData.getCode() - + "' to zip file.", ex); - return false; + throw new RuntimeException( + "Couldn't package meta data for data set '" + externalData.getCode() + "'.", ex); } - if (dataSetExistenceChecker.dataSetExists(DataSetTranslator - .translateToDescription(externalData)) == false) + if (dataSetExistenceChecker.dataSetExists(DataSetTranslator.translateToDescription(externalData)) == false) { - return handleNonExistingDataSet(externalData, null); + throw handleNonExistingDataSet(externalData, null); } IHierarchicalContent root = null; try @@ -75,17 +78,14 @@ public abstract class AbstractDataSetPackager root = contentProvider.asContent(externalData.getCode()); } catch (Exception ex) { - return handleNonExistingDataSet(externalData, ex); + throw handleNonExistingDataSet(externalData, ex); } try { addTo(rootPath, root.getRootNode()); - return true; } catch (Exception ex) { - logger.log(LogLevel.ERROR, "Couldn't add data set '" + externalData.getCode() - + "' to zip file.", ex); - return false; + throw new RuntimeException("Couldn't package data set '" + externalData.getCode() + "'.", ex); } finally { if (root != null) @@ -95,10 +95,9 @@ public abstract class AbstractDataSetPackager } } - private boolean handleNonExistingDataSet(AbstractExternalData externalData, Exception ex) + private RuntimeException handleNonExistingDataSet(AbstractExternalData externalData, Exception ex) { - logger.log(LogLevel.ERROR, "Data set " + externalData.getCode() + " does not exist.", ex); - return false; + return new RuntimeException("Data set '" + externalData.getCode() + "' does not exist.", ex); } private void addTo(String newRootPath, IHierarchicalContentNode node) @@ -116,6 +115,4 @@ public abstract class AbstractDataSetPackager node.getInputStream()); } } - - public abstract void addEntry(String entryPath, long lastModified, InputStream in); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java index a4a2d0106a6d01b942d349dbe23db3a802feadbc..0adce36e2a7005df0012e6dec7ad75fe496f802d 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommand.java @@ -17,9 +17,6 @@ package ch.systemsx.cisd.openbis.dss.generic.server; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -34,7 +31,6 @@ import ch.systemsx.cisd.cifex.rpc.client.ICIFEXComponent; import ch.systemsx.cisd.cifex.rpc.client.ICIFEXUploader; import ch.systemsx.cisd.cifex.rpc.client.gui.IProgressListener; import ch.systemsx.cisd.cifex.shared.basic.Constants; -import ch.systemsx.cisd.common.logging.Log4jSimpleLogger; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.mail.IMailClient; @@ -52,8 +48,6 @@ 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.dto.DataSetUploadContext; -import de.schlichtherle.util.zip.ZipOutputStream; - /** * A command which zips the given data sets and uploads the ZIP file to CIFEX. * @@ -278,24 +272,24 @@ class UploadingCommand implements IDataSetCommand private boolean fillZipFile(IDataSetDirectoryProvider dataSetDirectoryProvider, File zipFile) { - OutputStream outputStream = null; - ZipOutputStream zipOutputStream = null; + AbstractDataSetPackager packager = null; + DataSetExistenceChecker dataSetExistenceChecker = + new DataSetExistenceChecker(dataSetDirectoryProvider, + TimingParameters.create(new Properties())); try { - outputStream = new FileOutputStream(zipFile); - zipOutputStream = new ZipOutputStream(outputStream); - DataSetExistenceChecker dataSetExistenceChecker = - new DataSetExistenceChecker(dataSetDirectoryProvider, - TimingParameters.create(new Properties())); - Log4jSimpleLogger logger = new Log4jSimpleLogger(notificationLog); - ZipDataSetPackager packager = new ZipDataSetPackager(zipOutputStream, true, - logger, getHierarchicalContentProvider(), dataSetExistenceChecker); + packager = new ZipDataSetPackager(zipFile, true, + getHierarchicalContentProvider(), dataSetExistenceChecker); for (AbstractExternalData externalData : dataSets) { String newRootPath = createRootPath(externalData) + "/"; - boolean success = packager.addDataSetTo(newRootPath, externalData); - if (success == false) + try + { + + packager.addDataSetTo(newRootPath, externalData); + } catch (RuntimeException ex) { + notificationLog.error(ex.getMessage(), ex); return false; } } @@ -306,14 +300,14 @@ class UploadingCommand implements IDataSetCommand return false; } finally { - if (zipOutputStream != null) + if (packager != null) { try { - zipOutputStream.close(); - } catch (IOException ex) + packager.close(); + } catch (Exception ex) { - notificationLog.error("Couldn't close zip file", ex); + notificationLog.error("Couldn't close package", ex); } } } @@ -340,8 +334,7 @@ class UploadingCommand implements IDataSetCommand private void sendEMail(String message) { final IMailClient mailClient = new MailClient(mailClientParameters); - mailClient - .sendMessage("[Data Set Server] Uploading failed", message, null, null, userEMail); + mailClient.sendMessage("[Data Set Server] Uploading failed", message, null, null, userEMail); } @Override 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 e2972b4bfda7ca56933e52f216cd60f5276a0f54..a9088d310f07f04560404d39d1894a282e634f87 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 @@ -16,13 +16,14 @@ package ch.systemsx.cisd.openbis.dss.generic.server; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; -import ch.systemsx.cisd.common.logging.ISimpleLogger; import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DataSetExistenceChecker; @@ -36,14 +37,16 @@ import de.schlichtherle.util.zip.ZipOutputStream; */ public class ZipDataSetPackager extends AbstractDataSetPackager { - private final ZipOutputStream zipOutputStream; + private final File zipFile; private final boolean compress; + + private ZipOutputStream zipOutputStream; - protected ZipDataSetPackager(ZipOutputStream zipOutputStream, boolean compress, ISimpleLogger logger, + public ZipDataSetPackager(File zipFile, boolean compress, IHierarchicalContentProvider contentProvider, DataSetExistenceChecker dataSetExistenceChecker) { - super(logger, contentProvider, dataSetExistenceChecker); - this.zipOutputStream = zipOutputStream; + super(contentProvider, dataSetExistenceChecker); + this.zipFile = zipFile; this.compress = compress; } @@ -55,12 +58,12 @@ public class ZipDataSetPackager extends AbstractDataSetPackager ZipEntry zipEntry = new ZipEntry(entryPath.replace('\\', '/')); zipEntry.setTime(lastModified); zipEntry.setMethod(compress ? ZipEntry.DEFLATED : ZipEntry.STORED); - zipOutputStream.putNextEntry(zipEntry); + getZipOutputStream().putNextEntry(zipEntry); int len; byte[] buffer = new byte[1024]; while ((len = in.read(buffer)) > 0) { - zipOutputStream.write(buffer, 0, len); + getZipOutputStream().write(buffer, 0, len); } } catch (IOException ex) { @@ -70,7 +73,7 @@ public class ZipDataSetPackager extends AbstractDataSetPackager IOUtils.closeQuietly(in); try { - zipOutputStream.closeEntry(); + getZipOutputStream().closeEntry(); } catch (IOException ex) { throw CheckedExceptionTunnel.wrapIfNecessary(ex); @@ -78,4 +81,37 @@ public class ZipDataSetPackager extends AbstractDataSetPackager } } + @Override + public void close() + { + if (zipOutputStream != null) + { + try + { + zipOutputStream.close(); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + } + + private ZipOutputStream getZipOutputStream() + { + if (zipOutputStream == null) + { + FileOutputStream outputStream = null; + try + { + outputStream = new FileOutputStream(zipFile); + zipOutputStream = new ZipOutputStream(outputStream); + } catch (Exception ex) + { + IOUtils.closeQuietly(outputStream); + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + return zipOutputStream; + } + } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetFileOperationsManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetFileOperationsManager.java index cfe9d747af8b2d37e627a292bb02968b57a198b5..156f9f51eeb5ade68d99cc048b9ae4dde73ec2fd 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetFileOperationsManager.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetFileOperationsManager.java @@ -35,6 +35,8 @@ import ch.systemsx.cisd.common.filesystem.ssh.ISshCommandExecutor; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.properties.PropertyUtils; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.DefaultFileBasedHierarchicalContentFactory; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.openbis.dss.generic.server.IDataSetFileOperationsExecutor; import ch.systemsx.cisd.openbis.dss.generic.server.LocalDataSetFileOperationsExcecutor; import ch.systemsx.cisd.openbis.dss.generic.server.RemoteDataSetFileOperationsExecutor; @@ -353,8 +355,10 @@ public class DataSetFileOperationsManager implements IDataSetFileOperationsManag } @Override - public File getDestinationFile(DatasetDescription dataset) + public IHierarchicalContent getAsHierarchicalContent(DatasetDescription dataset) { - return new File(destination, dataset.getDataSetLocation()); + return new DefaultFileBasedHierarchicalContentFactory() + .asHierarchicalContent(new File(destination, dataset.getDataSetLocation()), null); } + } \ No newline at end of file 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 index ccb73e0e74785bc940465b4245ba1c1932cdcb8b..55d3b57d22890111009bebe007fb8d91fd39899d 100644 --- 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 @@ -21,13 +21,23 @@ 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.ServiceProvider; +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; /** * @@ -38,21 +48,91 @@ 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<String> dataSetCodes = new ArrayList<String>(); + 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) { - dataSetCodes.add(datasetDescription.getDataSetCode()); + 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); } - List<AbstractExternalData> dataSets = getService().listDataSetsByCode(dataSetCodes); - return null; + return dataSets; + } + + private File getArchive(AbstractExternalData dataSet) + { + return destination; } @Override @@ -72,15 +152,13 @@ public class DistributedArchiver extends AbstractArchiverProcessingPlugin @Override protected BooleanStatus isDataSetSynchronizedWithArchive(DatasetDescription dataset, ArchiverTaskContext context) { - // TODO Auto-generated method stub - return null; + return BooleanStatus.createFalse(); } @Override protected BooleanStatus isDataSetPresentInArchive(DatasetDescription dataset) { - // TODO Auto-generated method stub - return null; + return BooleanStatus.createFalse(); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedPackagingDataSetFileOperationsManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedPackagingDataSetFileOperationsManager.java new file mode 100644 index 0000000000000000000000000000000000000000..983e5e59a89efb13441193d163242c47c93fb7b8 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DistributedPackagingDataSetFileOperationsManager.java @@ -0,0 +1,363 @@ +/* + * 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.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.exceptions.Status; +import ch.systemsx.cisd.common.filesystem.BooleanStatus; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.properties.PropertyUtils; +import ch.systemsx.cisd.common.time.TimingParameters; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.FilteredHierarchicalContent; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.IHierarchicalContentNodeFilter; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.ZipBasedHierarchicalContent; +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.server.AbstractDataSetPackager; +import ch.systemsx.cisd.openbis.dss.generic.server.ZipDataSetPackager; +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.ServiceProvider; +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; + +import de.schlichtherle.io.rof.SimpleReadOnlyFile; +import de.schlichtherle.util.zip.BasicZipFile; +import de.schlichtherle.util.zip.ZipEntry; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class DistributedPackagingDataSetFileOperationsManager implements IDataSetFileOperationsManager +{ + static final String MAPPING_FILE_KEY = "mapping-file"; + + static final String CREATE_ARCHIVES_KEY = MAPPING_FILE_KEY + ".create-archives"; + + static final String DEFAULT_DESTINATION_KEY = "default-archive-folder"; + + static final String WITH_SHARDING_KEY = "with-sharding"; + + static final String COMPRESS_KEY = "compressing"; + + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, DistributedPackagingDataSetFileOperationsManager.class); + + private static final IHierarchicalContentNodeFilter FILTER = new IHierarchicalContentNodeFilter() + { + @Override + public boolean accept(IHierarchicalContentNode node) + { + return AbstractDataSetPackager.META_DATA_FILE_NAME.equals(node.getRelativePath()) == false; + } + }; + + private Map<String, File> spaceArchiveFolderMapping = new HashMap<String, File>(); + + private boolean compress; + + private File defaultFolder; + + private boolean withSharding; + + private transient IEncapsulatedOpenBISService service; + + private transient IHierarchicalContentProvider contentProvider; + + private transient IDataSetDirectoryProvider directoryProvider; + + public DistributedPackagingDataSetFileOperationsManager(Properties properties) + { + this(properties, null, null, null); + } + + DistributedPackagingDataSetFileOperationsManager(Properties properties, + IEncapsulatedOpenBISService service, IHierarchicalContentProvider contentProvider, + IDataSetDirectoryProvider directoryProvider) + { + this.service = service; + this.contentProvider = contentProvider; + this.directoryProvider = directoryProvider; + compress = PropertyUtils.getBoolean(properties, COMPRESS_KEY, true); + withSharding = PropertyUtils.getBoolean(properties, WITH_SHARDING_KEY, false); + defaultFolder = new File(PropertyUtils.getMandatoryProperty(properties, DEFAULT_DESTINATION_KEY)); + if (defaultFolder.isDirectory() == false) + { + throw new ConfigurationFailureException("Default archive folder '" + defaultFolder.getPath() + + "' doesn't exist or is not a folder."); + } + String mappingFilePath = properties.getProperty(MAPPING_FILE_KEY); + if (mappingFilePath != null) + { + boolean createArchives = PropertyUtils.getBoolean(properties, CREATE_ARCHIVES_KEY, false); + spaceArchiveFolderMapping = new SpaceAttributeMappingManager(mappingFilePath, createArchives).getFoldersMap(); + } + } + + @Override + public Status copyToDestination(File originalData, DatasetDescription datasetDescription) + { + AbstractExternalData dataSet = getDataSetWithAllMetaData(datasetDescription); + IShareIdManager shareIdManager = getDirectoryProvider().getShareIdManager(); + DataSetExistenceChecker dataSetExistenceChecker = + new DataSetExistenceChecker(getDirectoryProvider(), TimingParameters.create(new Properties())); + Status status = Status.OK; + String dataSetCode = datasetDescription.getDataSetCode(); + File file = getArchiveFile(datasetDescription); + shareIdManager.lock(dataSetCode); + AbstractDataSetPackager dataSetPackager = null; + try + { + dataSetPackager = createPackager(file, dataSetExistenceChecker); + dataSetPackager.addDataSetTo("", dataSet); + operationLog.info("Data set '" + dataSetCode + "' archived: " + file); + } 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); + } + return status; + } + + private AbstractDataSetPackager createPackager(File file, DataSetExistenceChecker dataSetExistenceChecker) + { + return new ZipDataSetPackager(file, compress, getContentProvider(), dataSetExistenceChecker); + } + + @Override + public Status retrieveFromDestination(File originalData, DatasetDescription datasetDescription) + { + File file = getArchiveFile(datasetDescription); + BasicZipFile zipFile = null; + FileOutputStream fileOutputStream = null; + try + { + zipFile = new BasicZipFile(new SimpleReadOnlyFile(file), "UTF-8", true, false); + @SuppressWarnings("unchecked") + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) + { + ZipEntry entry = entries.nextElement(); + File outputFile = new File(originalData, entry.getName()); + if (entry.isDirectory() == false + && AbstractDataSetPackager.META_DATA_FILE_NAME.equals(entry.getName()) == false) + { + outputFile.getParentFile().mkdirs(); + InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry)); + fileOutputStream = new FileOutputStream(outputFile); + BufferedOutputStream outputStream = new BufferedOutputStream(fileOutputStream); + try + { + IOUtils.copyLarge(inputStream, outputStream); + } finally + { + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outputStream); + } + } + } + operationLog.info("Data set '" + datasetDescription.getDataSetCode() + "' unzipped from archive '" + + file.getPath() + "' to '" + originalData + "'."); + return Status.OK; + } catch (Exception ex) + { + return Status.createError(ex.toString()); + } finally + { + if (zipFile != null) + { + try + { + zipFile.close(); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + IOUtils.closeQuietly(fileOutputStream); + } + } + + @Override + public Status deleteFromDestination(IDatasetLocation dataset) + { + File archiveFile = tryFindArchiveFile(dataset); + if (archiveFile == null) + { + operationLog.warn("Archive file for data set '" + dataset.getDataSetCode() + "' no konger exists."); + return Status.OK; + } + boolean success = archiveFile.delete(); + return success ? Status.OK : Status.createError("Couldn't delete archive file '" + archiveFile + "'."); + } + + private File tryFindArchiveFile(IDatasetLocation datasetLocation) + { + File archiveFile = getArchiveFile(defaultFolder, datasetLocation, false); + if (archiveFile.isFile()) + { + return archiveFile; + } + Collection<File> folders = spaceArchiveFolderMapping.values(); + for (File folder : folders) + { + archiveFile = getArchiveFile(folder, datasetLocation, false); + if (archiveFile.isFile()) + { + return archiveFile; + } + } + return null; + } + + @Override + public Status markAsDeleted(IDatasetLocation dataset) + { + return Status.OK; + } + + @Override + public BooleanStatus isSynchronizedWithDestination(File originalData, DatasetDescription datasetDescription) + { + return BooleanStatus.createFalse(); + } + + @Override + public BooleanStatus isPresentInDestination(DatasetDescription datasetDescription) + { + return BooleanStatus.createFalse(); + } + + @Override + public boolean isHosted() + { + return false; + } + + @Override + public IHierarchicalContent getAsHierarchicalContent(DatasetDescription dataset) + { + return new FilteredHierarchicalContent(new ZipBasedHierarchicalContent(getArchiveFile(dataset)), FILTER); + } + + private AbstractExternalData getDataSetWithAllMetaData(DatasetDescription datasetDescription) + { + AbstractExternalData dataSet = getService().tryGetDataSet(datasetDescription.getDataSetCode()); + String experimentIdentifier = datasetDescription.getExperimentIdentifier(); + dataSet.setExperiment(getService().tryGetExperiment(ExperimentIdentifierFactory.parse(experimentIdentifier))); + String sampleIdentifier = datasetDescription.getSampleIdentifier(); + if (sampleIdentifier != null) + { + dataSet.setSample(getService().tryGetSampleWithExperiment(SampleIdentifierFactory.parse(sampleIdentifier))); + } + return dataSet; + } + + private File getArchiveFile(DatasetDescription datasetDescription) + { + return getArchiveFile(datasetDescription.getSpaceCode(), datasetDescription, true); + } + + private File getArchiveFile(String spaceCode, IDatasetLocation datasetLocation, boolean forWriting) + { + File folder = spaceArchiveFolderMapping.get(spaceCode); + if (folder == null) + { + folder = defaultFolder; + } + return getArchiveFile(folder, datasetLocation, forWriting); + } + + private File getArchiveFile(File baseFolder, IDatasetLocation datasetLocation, boolean forWriting) + { + File folder = baseFolder; + if (withSharding) + { + folder = new File(folder, datasetLocation.getDataSetLocation()); + if (forWriting && folder.exists() == false) + { + folder.mkdirs(); + } + } + return new File(folder, datasetLocation.getDataSetCode() + ".zip"); + } + + private IEncapsulatedOpenBISService getService() + { + if (service == null) + { + service = ServiceProvider.getOpenBISService(); + } + return service; + } + + private IHierarchicalContentProvider getContentProvider() + { + if (contentProvider == null) + { + contentProvider = ServiceProvider.getHierarchicalContentProvider(); + } + return contentProvider; + } + + private IDataSetDirectoryProvider getDirectoryProvider() + { + if (directoryProvider == null) + { + directoryProvider = ServiceProvider.getDataStoreService().getDataSetDirectoryProvider(); + } + return directoryProvider; + } + +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IDataSetFileOperationsManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IDataSetFileOperationsManager.java index adb03a60ec7d3f6f838625d07d984fc6937eef73..65c005649e07ccb20212c0621f973205b51fe80c 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IDataSetFileOperationsManager.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IDataSetFileOperationsManager.java @@ -20,6 +20,7 @@ import java.io.File; import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.filesystem.BooleanStatus; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IDatasetLocation; import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; @@ -72,7 +73,7 @@ public interface IDataSetFileOperationsManager public boolean isHosted(); /** - * @return the dataset file in the destination location + * @return the dataset file in the destination location as a hierarchical content. */ - public File getDestinationFile(DatasetDescription dataset); + public IHierarchicalContent getAsHierarchicalContent(DatasetDescription dataset); } \ No newline at end of file diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/RsyncArchiver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/RsyncArchiver.java index 72fca00e0c3c094e8b7b6b438a5cc257daf4331a..44652fe4835964dc5f12908ae8637214ba9eaba7 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/RsyncArchiver.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/RsyncArchiver.java @@ -28,6 +28,8 @@ import ch.rinn.restrictions.Private; import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.filesystem.BooleanStatus; +import ch.systemsx.cisd.common.properties.PropertyParametersUtil; +import ch.systemsx.cisd.common.properties.PropertyParametersUtil.SectionProperties; import ch.systemsx.cisd.common.properties.PropertyUtils; import ch.systemsx.cisd.openbis.common.io.hierarchical_content.DefaultFileBasedHierarchicalContentFactory; import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; @@ -134,6 +136,19 @@ public class RsyncArchiver extends AbstractArchiverProcessingPlugin public abstract Status execute(IDataSetFileOperationsManager manager, IDatasetLocation dataSet); } + + private static final IDataSetFileOperationsManager createFileOperationsManager(Properties properties) + { + SectionProperties sectionProperties = + PropertyParametersUtil.extractSingleSectionProperties(properties, "distributed", false); + Properties props = sectionProperties.getProperties(); + if (props.isEmpty()) + { + return new DataSetFileOperationsManager(properties, + new RsyncArchiveCopierFactory(), new SshCommandExecutorFactory()); + } + return new DistributedPackagingDataSetFileOperationsManager(props); + } private transient IDataSetFileOperationsManager fileOperationsManager; @@ -143,8 +158,7 @@ public class RsyncArchiver extends AbstractArchiverProcessingPlugin public RsyncArchiver(Properties properties, File storeRoot) { - this(properties, storeRoot, new DataSetFileOperationsManager(properties, - new RsyncArchiveCopierFactory(), new SshCommandExecutorFactory())); + this(properties, storeRoot, createFileOperationsManager(properties)); } @Private RsyncArchiver(Properties properties, File storeRoot, @@ -201,11 +215,7 @@ public class RsyncArchiver extends AbstractArchiverProcessingPlugin .asHierarchicalContent(temp, null); } else { - archivedContent = - new DefaultFileBasedHierarchicalContentFactory() - .asHierarchicalContent( - fileOperationsManager.getDestinationFile(dataset), - null); + archivedContent = fileOperationsManager.getAsHierarchicalContent(dataset); } IHierarchicalContentNode root = content.getRootNode(); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManager.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManager.java new file mode 100644 index 0000000000000000000000000000000000000000..0a84942fa5ee5b93d45d93cf6fb4d3715f21eead --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManager.java @@ -0,0 +1,107 @@ +/* + * 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.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.csvreader.CsvReader; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; + +/** + * Helper class to read and provided space-related attributes. + * + * @author Franz-Josef Elmer + */ +public class SpaceAttributeMappingManager +{ + private final Map<String, File> folders = new HashMap<String, File>(); + + public SpaceAttributeMappingManager(String mappingFile, boolean createArchives) + { + CsvReader reader = null; + try + { + reader = new CsvReader(mappingFile, '\t'); + reader.setSkipEmptyRecords(true); + reader.setUseComments(false); + reader.setComment('#'); + reader.setTrimWhitespace(true); + boolean success = reader.readHeaders(); + if (success == false) + { + throw new IllegalArgumentException("Empty mapping file: " + mappingFile); + } + while (reader.readRecord()) + { + String[] row = reader.getValues(); + if (row.length != 3) + { + throw new IllegalArgumentException("Invalid number of row elements in mapping file '" + + mappingFile + "': " + Arrays.asList(row)); + } + String space = row[0].toUpperCase(); + folders.put(space, getArchiveFolder(space, row, createArchives)); + } + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } finally + { + if (reader != null) + { + reader.close(); + } + } + } + + private File getArchiveFolder(String space, String[] row, boolean createArchives) + { + File folder = new File(row[2]); + if (createArchives) + { + if (folder.isFile()) + { + throw new IllegalArgumentException("Archive folder '" + folder + "' is a file."); + } + if (folder.exists() == false) + { + boolean success = folder.mkdirs(); + if (success == false) + { + throw new IllegalArgumentException("Couldn't create archive folder '" + folder + "'."); + } + } + } else + { + if (folder.isDirectory() == false) + { + throw new IllegalArgumentException("Archive folder '" + folder + "' for space " + + space + " doesn't exists or is a file."); + } + } + return folder; + } + + public Map<String, File> getFoldersMap() + { + return folders; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/ArchiverPluginFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/ArchiverPluginFactory.java index 19556ab6f75fbb9d5a73ff5857ba02cb102bc689..2c2aad6dad288f66e603b8b7465eb2a45af3fff0 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/ArchiverPluginFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/ArchiverPluginFactory.java @@ -54,6 +54,18 @@ public class ArchiverPluginFactory this.archiverProperties = sectionProperties.getProperties(); } + /** + * Checks creation of archiver plugin is possible for specified store. + */ + public void check(File storeRoot) + { + if (isArchiverConfigured() == false) + { + return; + } + createInstance(storeRoot); + } + public boolean isArchiverConfigured() { return className != null; diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/PluginTaskInfoProvider.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/PluginTaskInfoProvider.java index 072d277d4966bc1e238d4958ad37b7f1b1d4148d..7597a248b9814440a819fb50ec21ff2d34fc4ca9 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/PluginTaskInfoProvider.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/PluginTaskInfoProvider.java @@ -126,6 +126,7 @@ public class PluginTaskInfoProvider implements IPluginTaskInfoProvider { processingPlugins.check(true); reportingPlugins.check(false); + archiverTaskFactory.check(storeRoot); } @Override diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommandTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommandTest.java index 06f9976644ab461f26973042431fdd1e01021af1..f4550881eccc5195b7723347fbf570dd4177d1d0 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommandTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/UploadingCommandTest.java @@ -432,7 +432,19 @@ public class UploadingCommandTest extends AssertJUnit logRecorder.resetLogContent(); command.execute(null, directoryProvider); - assertEquals("no emails expected", false, EMAILS.exists()); + if (EMAILS.exists()) + { + File[] files = EMAILS.listFiles(); + if (files != null) + { + StringBuilder builder = new StringBuilder(); + for (File file : files) + { + builder.append("\ne-mail:").append(FileUtilities.loadToString(file).trim()); + } + fail("Unexpected e-mail:" + builder); + } + } assertEquals(1, TMP.listFiles().length); checkZipFileContent(TMP.listFiles()[0]); assertEquals(INFO_UPLOAD_PREFIX @@ -535,11 +547,10 @@ public class UploadingCommandTest extends AssertJUnit command.execute(null, directoryProvider); checkEmail("Couldn't create zip file"); - assertEquals("WARN OPERATION.DataSetExistenceChecker - Data set '2' no longer exists." - + OSUtilities.LINE_SEPARATOR + - "ERROR NOTIFY.UploadingCommand - Data set 2 does not exist." - + OSUtilities.LINE_SEPARATOR + INFO_MAIL_PREFIX - + "Sending message from 'a@bc.de' to recipients '[user@bc.de]'", + assertEquals("WARN OPERATION.DataSetExistenceChecker - Data set '2' no longer exists." + OSUtilities.LINE_SEPARATOR + + "ERROR NOTIFY.UploadingCommand - Data set '2' does not exist." + OSUtilities.LINE_SEPARATOR + + "java.lang.RuntimeException: Data set '2' does not exist." + OSUtilities.LINE_SEPARATOR + + INFO_MAIL_PREFIX + "Sending message from 'a@bc.de' to recipients '[user@bc.de]'", getNormalizedLogContent()); context.assertIsSatisfied(); } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManagerTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bca98a9ea82956f374657a63403965b1add04615 --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/SpaceAttributeMappingManagerTest.java @@ -0,0 +1,62 @@ +/* + * 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.Map; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.filesystem.FileUtilities; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class SpaceAttributeMappingManagerTest extends AbstractFileSystemTestCase +{ + private File as1; + private File as2; + + @BeforeMethod + public void prepareTestFiles() + { + as1 = new File(workingDirectory, "a-s1"); + as1.mkdir(); + as2 = new File(workingDirectory, "a-s2"); + as2.mkdir(); + } + + @Test + public void test() + { + File mappingFile = new File(workingDirectory, "mapping.txt"); + FileUtilities.writeToFile(mappingFile, "Space\tLive Share\tArchive Folder\n" + + "s1\t2\t" + as1 + "\n" + + "s2\t4\t" + as2); + + SpaceAttributeMappingManager mappingManager = new SpaceAttributeMappingManager(mappingFile.getPath(), false); + + Map<String, File> foldersMap = mappingManager.getFoldersMap(); + assertEquals(as1.getPath(), foldersMap.get("S1").getPath()); + assertEquals(as2.getPath(), foldersMap.get("S2").getPath()); + } + +} diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/AbstractHierarchicalContent.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/AbstractHierarchicalContent.java new file mode 100644 index 0000000000000000000000000000000000000000..ad7d1572c0e44900ca0f73eaef491e6392999a84 --- /dev/null +++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/AbstractHierarchicalContent.java @@ -0,0 +1,95 @@ +/* + * 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.common.io.hierarchical_content; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContentNode; + +/** + * Abstract super class of {@link IHierarchicalContent} classes which implements {@link #listMatchingNodes(String)} + * and {@link #listMatchingNodes(String, String)}. + * + * @author Piotr Buczek + * @author Franz-Josef Elmer + */ +public abstract class AbstractHierarchicalContent implements IHierarchicalContent +{ + @Override + public List<IHierarchicalContentNode> listMatchingNodes(final String relativePathPattern) + { + final IHierarchicalContentNode startingNode = getRootNode(); + final Pattern compiledPattern = Pattern.compile(relativePathPattern); + final IHierarchicalContentNodeFilter relativePathFilter = + new IHierarchicalContentNodeFilter() + { + @Override + public boolean accept(IHierarchicalContentNode node) + { + return compiledPattern.matcher(node.getRelativePath()).matches(); + } + }; + + List<IHierarchicalContentNode> result = new ArrayList<IHierarchicalContentNode>(); + findMatchingNodes(startingNode, relativePathFilter, result); + return result; + } + + @Override + public List<IHierarchicalContentNode> listMatchingNodes(final String startingPath, final String fileNamePattern) + { + final IHierarchicalContentNode startingNode = getNode(startingPath); + final Pattern compiledPattern = Pattern.compile(fileNamePattern); + final IHierarchicalContentNodeFilter fileNameFilter = new IHierarchicalContentNodeFilter() + { + @Override + public boolean accept(IHierarchicalContentNode node) + { + return compiledPattern.matcher(node.getName()).matches(); + } + }; + + List<IHierarchicalContentNode> result = new ArrayList<IHierarchicalContentNode>(); + findMatchingNodes(startingNode, fileNameFilter, result); + return result; + } + + /** + * Recursively browses hierarchical content looking for nodes accepted by given + * <code>filter</code> and adding them to <code>result</code> list. + */ + private void findMatchingNodes(IHierarchicalContentNode dirNode, + IHierarchicalContentNodeFilter filter, List<IHierarchicalContentNode> result) + { + assert dirNode.isDirectory() : "expected a directory node, got: " + dirNode; + for (IHierarchicalContentNode childNode : dirNode.getChildNodes()) + { + if (filter.accept(childNode)) + { + result.add(childNode); + } + if (childNode.isDirectory()) + { + findMatchingNodes(childNode, filter, result); + } + } + } + +} \ No newline at end of file diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/DefaultFileBasedHierarchicalContent.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/DefaultFileBasedHierarchicalContent.java index 4c8026decd84fca3591833166f42fbf46657810e..a29276536e1c1446c2c0cfc318538bf629c18b25 100644 --- a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/DefaultFileBasedHierarchicalContent.java +++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/DefaultFileBasedHierarchicalContent.java @@ -17,9 +17,6 @@ package ch.systemsx.cisd.openbis.common.io.hierarchical_content; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; import ch.systemsx.cisd.common.action.IDelegatedAction; import ch.systemsx.cisd.common.filesystem.FileUtilities; @@ -34,7 +31,7 @@ import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchical * * @author Piotr Buczek */ -class DefaultFileBasedHierarchicalContent implements IHierarchicalContent +class DefaultFileBasedHierarchicalContent extends AbstractHierarchicalContent { private final IHierarchicalContentFactory hierarchicalContentFactory; @@ -129,46 +126,6 @@ class DefaultFileBasedHierarchicalContent implements IHierarchicalContent return hierarchicalContentFactory.asHierarchicalContentNode(this, file); } - @Override - public List<IHierarchicalContentNode> listMatchingNodes(final String relativePathPattern) - { - final IHierarchicalContentNode startingNode = getRootNode(); - final Pattern compiledPattern = Pattern.compile(relativePathPattern); - final IHierarchicalContentNodeFilter relativePathFilter = - new IHierarchicalContentNodeFilter() - { - @Override - public boolean accept(IHierarchicalContentNode node) - { - return compiledPattern.matcher(node.getRelativePath()).matches(); - } - }; - - List<IHierarchicalContentNode> result = new ArrayList<IHierarchicalContentNode>(); - findMatchingNodes(startingNode, relativePathFilter, result); - return result; - } - - @Override - public List<IHierarchicalContentNode> listMatchingNodes(final String startingPath, - final String fileNamePattern) - { - final IHierarchicalContentNode startingNode = getNode(startingPath); - final Pattern compiledPattern = Pattern.compile(fileNamePattern); - final IHierarchicalContentNodeFilter fileNameFilter = new IHierarchicalContentNodeFilter() - { - @Override - public boolean accept(IHierarchicalContentNode node) - { - return compiledPattern.matcher(node.getName()).matches(); - } - }; - - List<IHierarchicalContentNode> result = new ArrayList<IHierarchicalContentNode>(); - findMatchingNodes(startingNode, fileNameFilter, result); - return result; - } - @Override public void close() { @@ -226,25 +183,4 @@ class DefaultFileBasedHierarchicalContent implements IHierarchicalContent return true; } - /** - * Recursively browses hierarchical content looking for nodes accepted by given - * <code>filter</code> and adding them to <code>result</code> list. - */ - private static void findMatchingNodes(IHierarchicalContentNode dirNode, - IHierarchicalContentNodeFilter filter, List<IHierarchicalContentNode> result) - { - assert dirNode.isDirectory() : "expected a directory node, got: " + dirNode; - for (IHierarchicalContentNode childNode : dirNode.getChildNodes()) - { - if (filter.accept(childNode)) - { - result.add(childNode); - } - if (childNode.isDirectory()) - { - findMatchingNodes(childNode, filter, result); - } - } - } - } diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/FilteredHierarchicalContent.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/FilteredHierarchicalContent.java new file mode 100644 index 0000000000000000000000000000000000000000..4177f859bbca92d61b326d2803afbcf73c4646e0 --- /dev/null +++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/FilteredHierarchicalContent.java @@ -0,0 +1,193 @@ +/* + * 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.common.io.hierarchical_content; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked; +import ch.systemsx.cisd.base.io.IRandomAccessFile; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContentNode; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class FilteredHierarchicalContent implements IHierarchicalContent +{ + private final IHierarchicalContent content; + private final IHierarchicalContentNodeFilter filter; + + public FilteredHierarchicalContent(IHierarchicalContent content, IHierarchicalContentNodeFilter filter) + { + this.content = content; + this.filter = filter; + } + + @Override + public IHierarchicalContentNode getRootNode() + { + return wrap(content.getRootNode()); + } + + @Override + public IHierarchicalContentNode getNode(String relativePath) throws IllegalArgumentException + { + return wrap(content.getNode(relativePath)); + } + + @Override + public IHierarchicalContentNode tryGetNode(String relativePath) + { + return wrap(content.tryGetNode(relativePath)); + } + + @Override + public List<IHierarchicalContentNode> listMatchingNodes(String relativePathPattern) + { + return wrap(content.listMatchingNodes(relativePathPattern)); + } + + @Override + public List<IHierarchicalContentNode> listMatchingNodes(String startingPath, String fileNamePattern) + { + return wrap(content.listMatchingNodes(startingPath, fileNamePattern)); + } + + @Override + public void close() + { + content.close(); + } + + private List<IHierarchicalContentNode> wrap(List<IHierarchicalContentNode> nodes) + { + List<IHierarchicalContentNode> wrappedNodes = new ArrayList<IHierarchicalContentNode>(nodes.size()); + for (IHierarchicalContentNode node : nodes) + { + if (filter.accept(node)) + { + wrappedNodes.add(wrap(node)); + } + } + return wrappedNodes; + } + + private IHierarchicalContentNode wrap(IHierarchicalContentNode node) + { + return node == null ? null : new FilteredHierarchicalContentNode(node); + } + + private final class FilteredHierarchicalContentNode implements IHierarchicalContentNode + { + private final IHierarchicalContentNode node; + + FilteredHierarchicalContentNode(IHierarchicalContentNode node) + { + this.node = node; + } + + @Override + public String getName() + { + return node.getName(); + } + + @Override + public String getRelativePath() + { + return node.getRelativePath(); + } + + @Override + public String getParentRelativePath() + { + return node.getParentRelativePath(); + } + + @Override + public boolean exists() + { + return node.exists(); + } + + @Override + public boolean isDirectory() + { + return node.isDirectory(); + } + + @Override + public long getLastModified() + { + return node.getLastModified(); + } + + @Override + public List<IHierarchicalContentNode> getChildNodes() throws UnsupportedOperationException + { + return wrap(node.getChildNodes()); + } + + @Override + public File getFile() throws UnsupportedOperationException + { + return node.getFile(); + } + + @Override + public File tryGetFile() + { + return node.tryGetFile(); + } + + @Override + public long getFileLength() throws UnsupportedOperationException + { + return node.getFileLength(); + } + + @Override + public int getChecksumCRC32() throws UnsupportedOperationException + { + return node.getChecksumCRC32(); + } + + @Override + public boolean isChecksumCRC32Precalculated() + { + return node.isChecksumCRC32Precalculated(); + } + + @Override + public IRandomAccessFile getFileContent() throws UnsupportedOperationException, IOExceptionUnchecked + { + return node.getFileContent(); + } + + @Override + public InputStream getInputStream() throws UnsupportedOperationException, IOExceptionUnchecked + { + return node.getInputStream(); + } + } + +} diff --git a/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContent.java b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContent.java new file mode 100644 index 0000000000000000000000000000000000000000..7d87aebfddf75195511220910791d03ce5749bd9 --- /dev/null +++ b/openbis-common/source/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContent.java @@ -0,0 +1,310 @@ +/* + * 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.common.io.hierarchical_content; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.base.io.IRandomAccessFile; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContentNode; + +import de.schlichtherle.io.rof.SimpleReadOnlyFile; +import de.schlichtherle.util.zip.BasicZipFile; +import de.schlichtherle.util.zip.ZipEntry; + +/** + * {@link IHierarchicalContent} implementation for ZIP files. + * + * @author Franz-Josef Elmer + */ +public class ZipBasedHierarchicalContent extends AbstractHierarchicalContent +{ + private static final String extractName(String relativePath) + { + int indexOfLastDelimiter = relativePath.lastIndexOf('/'); + if (indexOfLastDelimiter < 0) + { + return relativePath; + } + return relativePath.substring(indexOfLastDelimiter + 1); + } + + private static final class ZipContainerNode extends AbstractHierarchicalDirectoryContentNode + { + private final String relativePath; + private final String name; + private final List<IHierarchicalContentNode> children = new ArrayList<IHierarchicalContentNode>(); + + ZipContainerNode(String relativePath) + { + this.relativePath = relativePath; + name = extractName(relativePath); + } + + @Override + public String getName() + { + return name; + } + + @Override + public boolean exists() + { + return true; + } + + @Override + public boolean isDirectory() + { + return true; + } + + @Override + public long getLastModified() + { + return 0; + } + + @Override + public File getFile() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(); + } + + @Override + public File tryGetFile() + { + return null; + } + + @Override + public boolean isChecksumCRC32Precalculated() + { + return true; + } + + @Override + protected String doGetRelativePath() + { + return relativePath; + } + + @Override + protected List<IHierarchicalContentNode> doGetChildNodes() + { + return new ArrayList<IHierarchicalContentNode>(children); + } + } + + private static final class ZipContentNode extends AbstractHierarchicalFileContentNode + { + private final BasicZipFile zipFile; + private final ZipEntry zipEntry; + private final String relativePath; + private final String name; + + ZipContentNode(BasicZipFile zipFile, ZipEntry zipEntry) + { + this.zipFile = zipFile; + this.zipEntry = zipEntry; + relativePath = zipEntry.getName(); + name = extractName(relativePath); + } + + @Override + public String getName() + { + return name; + } + + @Override + public boolean exists() + { + return true; + } + + @Override + public boolean isDirectory() + { + return false; + } + + @Override + public long getLastModified() + { + return zipEntry.getTime(); + } + + @Override + public File getFile() throws UnsupportedOperationException + { + throw new UnsupportedOperationException(); + } + + @Override + public File tryGetFile() + { + return null; + } + + @Override + protected String doGetRelativePath() + { + return relativePath; + } + + @Override + protected long doGetFileLength() + { + return zipEntry.getSize(); + } + + @Override + public boolean isChecksumCRC32Precalculated() + { + return true; + } + + @Override + protected int doGetChecksumCRC32() + { + return (int) zipEntry.getCrc(); + } + + @Override + protected IRandomAccessFile doGetFileContent() + { + throw new UnsupportedOperationException(); + } + + @Override + protected InputStream doGetInputStream() + { + try + { + return zipFile.getInputStream(zipEntry); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + } + + private static final class NodeManager + { + private final Map<String, ZipContainerNode> containerNodes = new HashMap<String, ZipContainerNode>(); + private final Map<String, IHierarchicalContentNode> allNodes = new HashMap<String, IHierarchicalContentNode>(); + + private ZipContainerNode rootNode; + + void handle(BasicZipFile zipFile, ZipEntry zipEntry) + { + linkNode(new ZipContentNode(zipFile, zipEntry)); + } + + private void linkNode(IHierarchicalContentNode contentNode) + { + allNodes.put(contentNode.getRelativePath(), contentNode); + String parentRelativePath = contentNode.getParentRelativePath(); + ZipContainerNode containerNode = containerNodes.get(parentRelativePath); + if (containerNode == null) + { + containerNode = new ZipContainerNode(parentRelativePath); + containerNodes.put(parentRelativePath, containerNode); + if (parentRelativePath.length() == 0) + { + rootNode = containerNode; + } else + { + linkNode(containerNode); + } + } + containerNode.children.add(contentNode); + } + } + + private final BasicZipFile zipFile; + private final IHierarchicalContentNode rootNode; + private final Map<String, IHierarchicalContentNode> allNodes; + + public ZipBasedHierarchicalContent(File file) + { + try + { + NodeManager nodeManager = new NodeManager(); + zipFile = new BasicZipFile(new SimpleReadOnlyFile(file), "UTF-8", true, false); + @SuppressWarnings("unchecked") + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) + { + ZipEntry entry = entries.nextElement(); + nodeManager.handle(zipFile, entry); + } + rootNode = nodeManager.rootNode; + allNodes = nodeManager.allNodes; + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + + @Override + public IHierarchicalContentNode getRootNode() + { + return rootNode; + } + + @Override + public IHierarchicalContentNode getNode(String relativePath) throws IllegalArgumentException + { + IHierarchicalContentNode node = tryGetNode(relativePath); + if (node == null) + { + throw new IllegalArgumentException("Resource '" + relativePath + "' does not exist."); + } + return node; + } + + @Override + public IHierarchicalContentNode tryGetNode(String relativePath) + { + return allNodes.get(relativePath); + } + + @Override + public void close() + { + try + { + zipFile.close(); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + + +} diff --git a/openbis-common/sourceTest/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContentTest.java b/openbis-common/sourceTest/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContentTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ae39f426ceba8941b24be5eb7878fe16d000bbfc --- /dev/null +++ b/openbis-common/sourceTest/java/ch/systemsx/cisd/openbis/common/io/hierarchical_content/ZipBasedHierarchicalContentTest.java @@ -0,0 +1,199 @@ +/* + * 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.common.io.hierarchical_content; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.zip.CRC32; +import java.util.zip.CheckedInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.lang.StringUtils; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.openbis.common.io.hierarchical_content.api.IHierarchicalContentNode; + +import de.schlichtherle.util.zip.ZipEntry; +import de.schlichtherle.util.zip.ZipOutputStream; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class ZipBasedHierarchicalContentTest extends AbstractFileSystemTestCase +{ + + @Test + public void test() throws Exception + { + File dataRoot = new File(workingDirectory, "data"); + File alpha = new File(dataRoot, "alpha"); + File beta = new File(alpha, "beta"); + beta.mkdirs(); + FileUtilities.writeToFile(new File(beta, "hello.txt"), "hello world!"); + FileUtilities.writeToFile(new File(alpha, "read.me"), "don't read me!"); + FileUtilities.writeToFile(new File(dataRoot, "change-log.txt"), "nothing really changed."); + File zipFile = new File("data.zip"); + zip(zipFile, dataRoot); + + IHierarchicalContent content = new ZipBasedHierarchicalContent(zipFile); + + IHierarchicalContentNode rootNode = content.getRootNode(); + assertDirectoryNode("", "", rootNode); + List<IHierarchicalContentNode> childNodes = rootNode.getChildNodes(); + assertDirectoryNode("alpha", "alpha", childNodes.get(0)); + List<IHierarchicalContentNode> grandChildNodes = childNodes.get(0).getChildNodes(); + assertDirectoryNode("alpha/beta", "beta", grandChildNodes.get(0)); + List<IHierarchicalContentNode> grandGrandChildNodes = grandChildNodes.get(0).getChildNodes(); + assertFileNode("alpha/beta/hello.txt", "hello.txt", "hello world!", grandGrandChildNodes.get(0)); + assertEquals(1, grandGrandChildNodes.size()); + assertFileNode("alpha/read.me", "read.me", "don't read me!", grandChildNodes.get(1)); + assertEquals(2, grandChildNodes.size()); + assertFileNode("change-log.txt", "change-log.txt", "nothing really changed.", childNodes.get(1)); + assertEquals(2, childNodes.size()); + } + + private void assertDirectoryNode(String expectedPath, String expectedName, IHierarchicalContentNode node) + { + assertPathAndName(expectedPath, expectedName, node); + assertEquals(true, node.isDirectory()); + } + + private void assertFileNode(String expectedPath, String expectedName, String expectedContent, + IHierarchicalContentNode node) throws Exception + { + assertPathAndName(expectedPath, expectedName, node); + assertEquals(false, node.isDirectory()); + assertEquals(expectedContent.length(), node.getFileLength()); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + IOUtils.copy(node.getInputStream(), output); + assertEquals(expectedContent, output.toString()); + CRC32 checksum = new CRC32(); + CheckedInputStream in = new CheckedInputStream(new ByteArrayInputStream(expectedContent.getBytes()), checksum); + IOUtils.copy(in, new NullOutputStream()); + assertEquals(checksum.getValue(), node.getChecksumCRC32()); + + } + + private void assertPathAndName(String expectedPath, String expectedName, IHierarchicalContentNode node) + { + assertEquals(expectedPath, node.getRelativePath()); + assertEquals(expectedName, node.getName()); + String parentRelativePath = node.getParentRelativePath(); + if (expectedPath.equals("")) + { + assertEquals(null, parentRelativePath); + } else + { + assertEquals(expectedPath, + (StringUtils.isBlank(parentRelativePath) ? "" : parentRelativePath + "/") + node.getName()); + } + } + + private void zip(File zipFile, File folder) + { + OutputStream outputStream = null; + ZipOutputStream zipOutputStream = null; + try + { + outputStream = new FileOutputStream(zipFile); + zipOutputStream = new ZipOutputStream(outputStream); + zip(zipOutputStream, folder, folder); + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } finally + { + if (zipOutputStream != null) + { + try + { + zipOutputStream.close(); + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } else + { + IOUtils.closeQuietly(outputStream); + } + } + } + + private void zip(ZipOutputStream zipOutputStream, File rootFile, File file) + { + if (file.isFile()) + { + zipTo(zipOutputStream, rootFile, file); + } else if (file.isDirectory()) + { + File[] files = file.listFiles(); + Arrays.sort(files); + for (File childFile : files) + { + zip(zipOutputStream, rootFile, childFile); + } + } + } + + private void zipTo(ZipOutputStream zipOutputStream, File rootFile, File file) + { + long lastModified = file.lastModified(); + FileInputStream in = null; + try + { + in = new FileInputStream(file); + ZipEntry zipEntry = new ZipEntry(FileUtilities.getRelativeFilePath(rootFile, file).replace('\\', '/')); + zipEntry.setTime(lastModified); + zipEntry.setMethod(ZipEntry.DEFLATED); + zipOutputStream.putNextEntry(zipEntry); + int len; + byte[] buffer = new byte[1024]; + while ((len = in.read(buffer)) > 0) + { + zipOutputStream.write(buffer, 0, len); + } + } catch (Exception ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } finally + { + IOUtils.closeQuietly(in); + try + { + zipOutputStream.closeEntry(); + } catch (IOException ex) + { + throw CheckedExceptionTunnel.wrapIfNecessary(ex); + } + } + } + +}