diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/Copier.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/Copier.java index 402dd6fb041663f4cf8eb5a39610a648e24185a8..74355340ebcb93127cfb67d89e4decc7eed262e6 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/Copier.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/Copier.java @@ -29,6 +29,7 @@ import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.filesystem.BooleanStatus; import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.filesystem.HostAwareFile; +import ch.systemsx.cisd.common.filesystem.IImmutableCopier; import ch.systemsx.cisd.common.filesystem.IPathCopier; import ch.systemsx.cisd.common.filesystem.ssh.ISshCommandExecutor; import ch.systemsx.cisd.common.highwatermark.HostAwareFileWithHighwaterMark; @@ -50,6 +51,8 @@ public class Copier implements Serializable, IPostRegistrationDatasetHandler private final File sshExecutable; + private File lnExecutable; + private final String hostFile; private final String rsyncPasswordFile; @@ -58,18 +61,36 @@ public class Copier implements Serializable, IPostRegistrationDatasetHandler private final ISshCommandExecutorFactory sshCommandExecutorFactory; + private final IImmutableCopierFactory immutableCopierFactory; + private final boolean renameToDataSetCode; + private final boolean hardLinkCopy; + public Copier(Properties properties, IPathCopierFactory pathCopierFactory, - ISshCommandExecutorFactory sshCommandExecutorFactory) + ISshCommandExecutorFactory sshCommandExecutorFactory, + IImmutableCopierFactory immutableCopierFactory) { this.pathCopierFactory = pathCopierFactory; this.sshCommandExecutorFactory = sshCommandExecutorFactory; + this.immutableCopierFactory = immutableCopierFactory; rsyncPasswordFile = properties.getProperty(DataSetCopier.RSYNC_PASSWORD_FILE_KEY); rsyncExecutable = getExecutable(properties, DataSetCopier.RSYNC_EXEC); sshExecutable = getExecutable(properties, DataSetCopier.SSH_EXEC); hostFile = PropertyUtils.getMandatoryProperty(properties, DataSetCopier.DESTINATION_KEY); - renameToDataSetCode = PropertyUtils.getBoolean(properties, "rename-to-dataset-code", false); + renameToDataSetCode = PropertyUtils.getBoolean(properties, DataSetCopier.RENAME_TO_DATASET_CODE_KEY, false); + hardLinkCopy = PropertyUtils.getBoolean(properties, DataSetCopier.HARD_LINK_COPY_KEY, false); + if (hardLinkCopy) + { + String host = HostAwareFileWithHighwaterMark.create(hostFile, -1).tryGetHost(); + if (host != null) + { + throw new ConfigurationFailureException( + "Hard link copying not possible on an unmounted destination on host '" + + host + "'."); + } + lnExecutable = getExecutable(properties, DataSetCopier.LN_EXEC); + } } protected String transformHostFile(String originalHostFile, @@ -100,24 +121,37 @@ public class Copier implements Serializable, IPostRegistrationDatasetHandler } File destination = hostAwareFile.getFile(); File destinationFile = new File(destination, originalData.getName()); + String dataSetCode = dataSetInformation.getDataSetCode(); + File finalDestinationFile = new File(destination, renameToDataSetCode ? dataSetCode : originalData.getName()); BooleanStatus destinationExists = - checkDestinationFileExistence(destinationFile, host, sshCommandExecutor); + checkDestinationFileExistence(finalDestinationFile, host, sshCommandExecutor); if (destinationExists.isSuccess()) { - operationLog.error("Destination file/directory '" + destinationFile.getPath() + operationLog.error("Destination file/directory '" + finalDestinationFile.getPath() + "' already exists - dataset files will not be copied."); return Status.createError(DataSetCopier.ALREADY_EXIST_MSG); } else if (destinationExists.isError()) { - operationLog.error("Could not test destination file/directory '" + destinationFile + operationLog.error("Could not test destination file/directory '" + finalDestinationFile + "' existence" + (host != null ? " on host '" + host + "'" : "") + ": " + destinationExists.tryGetMessage()); return Status.createError(DataSetCopier.COPYING_FAILED_MSG); } - Status status = - copier.copyToRemote(originalData, destination, host, rsyncModule, rsyncPasswordFile); - String dataSetCode = dataSetInformation.getDataSetCode(); + Status status; + if (hardLinkCopy) + { + IImmutableCopier hardLinkMaker = immutableCopierFactory.create(rsyncExecutable, + lnExecutable); + status = + hardLinkMaker.copyImmutably(originalData, destination, + renameToDataSetCode ? dataSetCode : null); + } else + { + status = + copier.copyToRemote(originalData, destination, host, rsyncModule, + rsyncPasswordFile); + } if (status.isError()) { operationLog.error("Could not copy data set " + dataSetCode @@ -127,20 +161,19 @@ public class Copier implements Serializable, IPostRegistrationDatasetHandler + status); return Status.createError(DataSetCopier.COPYING_FAILED_MSG); } - if (renameToDataSetCode) + if (hardLinkCopy == false && renameToDataSetCode) { - File newFile = new File(destination, dataSetCode); if (host == null) { - if (destinationFile.renameTo(newFile)) + if (destinationFile.renameTo(finalDestinationFile) == false) { operationLog.error("Moving of '" + destinationFile.getPath() + "' to '" - + newFile + "' failed."); - return Status.createError("couldn't move"); + + finalDestinationFile + "' failed."); + return Status.createError("couldn't rename"); } } else { - String newFilePath = newFile.getPath(); + String newFilePath = finalDestinationFile.getPath(); ProcessResult result = sshCommandExecutor.executeCommandRemotely("mv " + destinationFile.getPath() + " " + newFilePath, DataSetCopier.SSH_TIMEOUT_MILLIS); @@ -148,7 +181,7 @@ public class Copier implements Serializable, IPostRegistrationDatasetHandler { operationLog.error("Remote move of '" + destinationFile.getPath() + "' to '" + newFilePath + "' failed with exit value: " + result.getExitValue()); - return Status.createError("couldn't move"); + return Status.createError("couldn't rename"); } } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopier.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopier.java index 5eeb862016aa2af3d7dd702f39bef018a94e7002..530eac02899c7d3bf28aa3df645ffb2868bcbe43 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopier.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopier.java @@ -44,6 +44,10 @@ public class DataSetCopier extends AbstractDropboxProcessingPlugin public static final String RSYNC_PASSWORD_FILE_KEY = "rsync-password-file"; + public static final String RENAME_TO_DATASET_CODE_KEY = "rename-to-dataset-code"; + + public static final String HARD_LINK_COPY_KEY = "hard-link-copy"; + @Private static final String ALREADY_EXIST_MSG = "already exist"; @@ -54,6 +58,8 @@ public class DataSetCopier extends AbstractDropboxProcessingPlugin public static final String RSYNC_EXEC = "rsync"; + public static final String LN_EXEC = "ln"; + public static final String SSH_EXEC = "ssh"; public static final long SSH_TIMEOUT_MILLIS = 15 * 1000; // 15s @@ -61,15 +67,16 @@ public class DataSetCopier extends AbstractDropboxProcessingPlugin public DataSetCopier(Properties properties, File storeRoot) { this(properties, storeRoot, new RsyncCopierFactory(), new SshCommandExecutorFactory(), - SystemTimeProvider.SYSTEM_TIME_PROVIDER); + new ImmutableCopierFactory(), SystemTimeProvider.SYSTEM_TIME_PROVIDER); } @Private DataSetCopier(Properties properties, File storeRoot, IPathCopierFactory pathCopierFactory, - ISshCommandExecutorFactory sshCommandExecutorFactory, ITimeProvider timeProvider) + ISshCommandExecutorFactory sshCommandExecutorFactory, + IImmutableCopierFactory immutableCopierFactory, ITimeProvider timeProvider) { super(properties, storeRoot, new Copier(properties, pathCopierFactory, - sshCommandExecutorFactory), timeProvider); + sshCommandExecutorFactory, immutableCopierFactory), timeProvider); } public DataSetCopier(Properties properties, File storeRoot, diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsers.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsers.java index cb848d1455b7e6cf9d2ab35f5f720db9e9524a1d..86d3369dd8f92084f8db9ef8e51a1f8789466ca1 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsers.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsers.java @@ -37,16 +37,16 @@ public class DataSetCopierForUsers extends DataSetCopier public DataSetCopierForUsers(Properties properties, File storeRoot) { - this(properties, storeRoot, new RsyncCopierFactory(), new SshCommandExecutorFactory()); + this(properties, storeRoot, new RsyncCopierFactory(), new SshCommandExecutorFactory(), new ImmutableCopierFactory()); } @Private DataSetCopierForUsers(Properties properties, File storeRoot, IPathCopierFactory pathCopierFactory, - ISshCommandExecutorFactory sshCommandExecutorFactory) + ISshCommandExecutorFactory sshCommandExecutorFactory, IImmutableCopierFactory immutableCopierFactory) { super(properties, storeRoot, new Copier(properties, pathCopierFactory, - sshCommandExecutorFactory) + sshCommandExecutorFactory, immutableCopierFactory) { private static final long serialVersionUID = 1L; diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IImmutableCopierFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IImmutableCopierFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..a51f287a9ba1a91ca94b08b251443c79a1c189dc --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/IImmutableCopierFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012 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 ch.systemsx.cisd.common.filesystem.IImmutableCopier; + +/** + * Factory for creating an {@link IImmutableCopier} instance. + * + * @author Franz-Josef Elmer + */ +public interface IImmutableCopierFactory +{ + public IImmutableCopier create(File rsyncExecutable, File lnExecutable); +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/ImmutableCopierFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/ImmutableCopierFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..bf942314e1a9044809f0a090cc98382f9af7d3aa --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/ImmutableCopierFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 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.io.Serializable; + +import ch.systemsx.cisd.common.filesystem.FastRecursiveHardLinkMaker; +import ch.systemsx.cisd.common.filesystem.IImmutableCopier; + +/** + * Factory for {@link IImmutableCopier} instances based on {@link FastRecursiveHardLinkMaker}. + * + * @author Franz-Josef Elmer + */ +public class ImmutableCopierFactory implements IImmutableCopierFactory, Serializable +{ + + private static final long serialVersionUID = 1L; + + @Override + public IImmutableCopier create(File rsyncExecutable, File lnExecutable) + { + return FastRecursiveHardLinkMaker.create(rsyncExecutable, lnExecutable); + } + +} diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java index 2b285aa28d674904c8c0ffbd4b1ff40e7594c8f3..45929b766e017d821e2cdba9b02f094ed6b1c778 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java @@ -66,6 +66,8 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase private ISshCommandExecutor sshCommandExecutor; + private IImmutableCopierFactory hardLinkMakerFactory; + private File storeRoot; private File sshExecutableDummy; @@ -86,6 +88,7 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase context = new Mockery(); pathFactory = context.mock(IPathCopierFactory.class); sshFactory = context.mock(ISshCommandExecutorFactory.class); + hardLinkMakerFactory = context.mock(IImmutableCopierFactory.class); copier = context.mock(IPathCopier.class); sshCommandExecutor = context.mock(ISshCommandExecutor.class); storeRoot = new File(workingDirectory, "store"); @@ -135,7 +138,8 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase } }); DataSetCopier dataSetCopier = - new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory); + new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory, + hardLinkMakerFactory); ProcessingStatus processingStatus = dataSetCopier.process(Arrays.asList(ds), dataSetProcessingContext); @@ -159,7 +163,8 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase } }); DataSetCopier dataSetCopier = - new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory); + new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory, + hardLinkMakerFactory); ProcessingStatus processingStatus = dataSetCopier.process(Arrays.asList(ds), dataSetProcessingContext); diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierTest.java index 35ec268d640a8c6bf9824074dc8d1ba338fac90a..c4133b8c27ed1afba798e2df0face159433885b1 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierTest.java @@ -17,6 +17,8 @@ package ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard; import static ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.DataSetCopier.DESTINATION_KEY; +import static ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.DataSetCopier.HARD_LINK_COPY_KEY; +import static ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.DataSetCopier.RENAME_TO_DATASET_CODE_KEY; import static ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.DataSetCopier.RSYNC_PASSWORD_FILE_KEY; import java.io.File; @@ -39,6 +41,7 @@ import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; 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.filesystem.IImmutableCopier; import ch.systemsx.cisd.common.filesystem.IPathCopier; import ch.systemsx.cisd.common.filesystem.ssh.ISshCommandExecutor; import ch.systemsx.cisd.common.mail.EMailAddress; @@ -56,6 +59,8 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; { DataSetCopier.class, AbstractDropboxProcessingPlugin.class }) public class DataSetCopierTest extends AbstractFileSystemTestCase { + private static final String RSYNC_EXECUTABLE = "rsync-executable"; + private static final String SHARE_ID = "42"; private static final String USER_EMAIL = "a@bc.de"; @@ -83,6 +88,8 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase private File sshExecutableDummy; private File rsyncExecutableDummy; + + private File lnExecutableDummy; private Properties properties; @@ -108,6 +115,10 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase private IMailClient mailClient; + private IImmutableCopier hardLinkMaker; + + private IImmutableCopierFactory hardLinkMakerFactory; + @BeforeMethod public void beforeMethod() throws IOException { @@ -117,6 +128,8 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase pathFactory = context.mock(IPathCopierFactory.class); sshFactory = context.mock(ISshCommandExecutorFactory.class); copier = context.mock(IPathCopier.class); + hardLinkMakerFactory = context.mock(IImmutableCopierFactory.class); + hardLinkMaker = context.mock(IImmutableCopier.class); sshCommandExecutor = context.mock(ISshCommandExecutor.class); storeRoot = new File(workingDirectory, "store"); storeRoot.mkdirs(); @@ -124,9 +137,12 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase sshExecutableDummy.createNewFile(); rsyncExecutableDummy = new File(workingDirectory, "my-rsync"); rsyncExecutableDummy.createNewFile(); + lnExecutableDummy = new File(workingDirectory, "my-ln"); + lnExecutableDummy.createNewFile(); properties = new Properties(); properties.setProperty("ssh-executable", sshExecutableDummy.getPath()); - properties.setProperty("rsync-executable", rsyncExecutableDummy.getPath()); + properties.setProperty(RSYNC_EXECUTABLE, rsyncExecutableDummy.getPath()); + properties.setProperty("ln-executable", lnExecutableDummy.getPath()); ds1 = createDataSetDescription("ds1", DS1_LOCATION, true); File share = new File(storeRoot, SHARE_ID); File ds1Folder = new File(share, DS1_LOCATION + "/original"); @@ -462,22 +478,21 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase } @Test - public void testCopyLocallyFails() + public void testCopyLocallyFailsBecauseDestinationExists() { - properties.setProperty(DESTINATION_KEY, "tmp/test"); - - File existingDestinationDir = new File("tmp/test/existing"); + final File destination = new File(workingDirectory, "tmp/test"); + properties.setProperty(DESTINATION_KEY, destination.getPath()); + File existingDestinationDir = new File(destination, "existing"); existingDestinationDir.mkdirs(); - prepareCreateAndCheckCopier(null, null, 4, true); context.checking(new Expectations() { { - one(copier).copyToRemote(ds1Data, getCanonicalFile("tmp/test"), null, null, + one(copier).copyToRemote(ds1Data, getCanonicalFile(destination), null, null, null); will(returnValue(Status.createError("error message"))); - one(copier).copyToRemote(ds2Data, getCanonicalFile("tmp/test"), null, null, + one(copier).copyToRemote(ds2Data, getCanonicalFile(destination), null, null, null); will(returnValue(Status.OK)); } @@ -495,7 +510,25 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase Status alreadyExistStatus = Status.createError(DataSetCopier.ALREADY_EXIST_MSG); assertError(processingStatus, alreadyExistStatus, ds3, ds4); - existingDestinationDir.delete(); + context.assertIsSatisfied(); + } + + @Test + public void testCopyLocallyWithRenamingFailsBecauseDestinationExists() + { + final File destination = new File(workingDirectory, "tmp/test"); + properties.setProperty(DESTINATION_KEY, destination.getPath()); + properties.setProperty(RENAME_TO_DATASET_CODE_KEY, "true"); + File existingDestinationDir = new File(destination, ds1.getDataSetCode()); + existingDestinationDir.mkdirs(); + prepareCreateAndCheckCopier(null, null, 1, true); + DataSetCopier dataSetCopier = createCopier(); + + ProcessingStatus processingStatus = + dataSetCopier.process(Arrays.asList(ds1), dummyContext); + + Status errorStatus = Status.createError(DataSetCopier.ALREADY_EXIST_MSG); + assertError(processingStatus, errorStatus, ds1); context.assertIsSatisfied(); } @@ -631,6 +664,113 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase context.assertIsSatisfied(); } + + @Test + public void testHardLinkCopyingNotPossibleForRemoteDestinations() + { + properties.setProperty(DESTINATION_KEY, "host:tmp/test"); + properties.setProperty(HARD_LINK_COPY_KEY, "true"); + + try + { + createCopier(); + fail("ConfigurationFailureException expected"); + } catch (ConfigurationFailureException ex) + { + assertEquals("Hard link copying not possible on an unmounted destination " + + "on host 'host'.", ex.getMessage()); + } + + context.assertIsSatisfied(); + } + + @Test + public void testHardLinkCopyingSucessfully() + { + properties.setProperty(DESTINATION_KEY, "tmp/test"); + properties.setProperty(HARD_LINK_COPY_KEY, "true"); + properties.setProperty(AbstractDropboxProcessingPlugin.SEND_DETAILED_EMAIL_KEY, "true"); + prepareCreateAndCheckCopier(null, null, 1, true); + final RecordingMatcher<String> subjectRecorder = new RecordingMatcher<String>(); + final RecordingMatcher<String> contentRecorder = new RecordingMatcher<String>(); + final RecordingMatcher<EMailAddress[]> recipientsRecorder = + new RecordingMatcher<EMailAddress[]>(); + context.checking(new Expectations() + { + { + one(hardLinkMakerFactory).create(rsyncExecutableDummy, lnExecutableDummy); + will(returnValue(hardLinkMaker)); + + one(hardLinkMaker).copyImmutably(ds1Data, getCanonicalFile("tmp/test"), null); + will(returnValue(Status.OK)); + + one(mailClient).sendEmailMessage(with(subjectRecorder), with(contentRecorder), + with(new IsNull<EMailAddress>()), with(new IsNull<EMailAddress>()), + with(recipientsRecorder)); + } + }); + DataSetCopier dataSetCopier = createCopier(); + + ProcessingStatus processingStatus = + dataSetCopier.process(Arrays.asList(ds1), dummyContext); + + assertNoErrors(processingStatus); + assertSuccessful(processingStatus, ds1); + assertEquals(USER_EMAIL, recipientsRecorder.recordedObject()[0].tryGetEmailAddress()); + assertEquals("Data set ds1 [MY_DATA] successfully processed", + subjectRecorder.recordedObject()); + assertEquals("Successfully processed data set ds1 [MY_DATA].\n\n" + "Processing details:\n" + + "Description: Copy to tmp/test\n" + "Experiment: /g/p/e [MY_EXPERIMENT]\n" + + "Sample: /g/s [MY_SAMPLE]\n" + "Started: 1970-01-01 01:00:00 +0100.\n" + + "Finished: 1970-01-01 01:00:00 +0100.", contentRecorder.recordedObject()); + + context.assertIsSatisfied(); + } + + + @Test + public void testHardLinkCopyingWithRenamingSucessfully() + { + properties.setProperty(DESTINATION_KEY, "tmp/test"); + properties.setProperty(HARD_LINK_COPY_KEY, "true"); + properties.setProperty(RENAME_TO_DATASET_CODE_KEY, "true"); + properties.setProperty(AbstractDropboxProcessingPlugin.SEND_DETAILED_EMAIL_KEY, "true"); + prepareCreateAndCheckCopier(null, null, 1, true); + final RecordingMatcher<String> subjectRecorder = new RecordingMatcher<String>(); + final RecordingMatcher<String> contentRecorder = new RecordingMatcher<String>(); + final RecordingMatcher<EMailAddress[]> recipientsRecorder = + new RecordingMatcher<EMailAddress[]>(); + context.checking(new Expectations() + { + { + one(hardLinkMakerFactory).create(rsyncExecutableDummy, lnExecutableDummy); + will(returnValue(hardLinkMaker)); + + one(hardLinkMaker).copyImmutably(ds1Data, getCanonicalFile("tmp/test"), + ds1.getDataSetCode()); + will(returnValue(Status.OK)); + + one(mailClient).sendEmailMessage(with(subjectRecorder), with(contentRecorder), + with(new IsNull<EMailAddress>()), with(new IsNull<EMailAddress>()), + with(recipientsRecorder)); + } + }); + DataSetCopier dataSetCopier = createCopier(); + + ProcessingStatus processingStatus = dataSetCopier.process(Arrays.asList(ds1), dummyContext); + + assertNoErrors(processingStatus); + assertSuccessful(processingStatus, ds1); + assertEquals(USER_EMAIL, recipientsRecorder.recordedObject()[0].tryGetEmailAddress()); + assertEquals("Data set ds1 [MY_DATA] successfully processed", + subjectRecorder.recordedObject()); + assertEquals("Successfully processed data set ds1 [MY_DATA].\n\n" + "Processing details:\n" + + "Description: Copy to tmp/test\n" + "Experiment: /g/p/e [MY_EXPERIMENT]\n" + + "Sample: /g/s [MY_SAMPLE]\n" + "Started: 1970-01-01 01:00:00 +0100.\n" + + "Finished: 1970-01-01 01:00:00 +0100.", contentRecorder.recordedObject()); + + context.assertIsSatisfied(); + } private void prepareCreateAndCheckCopier(final String hostOrNull, final String rsyncModuleOrNull, final int numberOfExpectedCreations, @@ -683,7 +823,9 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase private void assertError(ProcessingStatus processingStatus, Status errorStatus, DatasetDescription... datasets) { - processingStatus.getErrorStatuses().contains(errorStatus); + List<Status> errorStatuses = processingStatus.getErrorStatuses(); + assertEquals("Error statuses " + errorStatuses + " dosn't contain " + errorStatus, true, + errorStatuses.contains(errorStatus)); checkStatus(processingStatus, errorStatus, datasets); } @@ -706,10 +848,15 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase } private File getCanonicalFile(String fileName) + { + return getCanonicalFile(new File(fileName)); + } + + private File getCanonicalFile(File file) { try { - return new File(fileName).getCanonicalFile(); + return file.getCanonicalFile(); } catch (IOException ex) { throw CheckedExceptionTunnel.wrapIfNecessary(ex); @@ -718,7 +865,8 @@ public class DataSetCopierTest extends AbstractFileSystemTestCase private DataSetCopier createCopier() { - return new DataSetCopier(properties, storeRoot, pathFactory, sshFactory, timeProvider); + return new DataSetCopier(properties, storeRoot, pathFactory, sshFactory, + hardLinkMakerFactory, timeProvider); } }