diff --git a/common/source/java/ch/systemsx/cisd/common/test/InvocationRecordingWrapper.java b/common/source/java/ch/systemsx/cisd/common/test/InvocationRecordingWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..6061013b4f68fbe177659c9fce1100ea35ba4810 --- /dev/null +++ b/common/source/java/ch/systemsx/cisd/common/test/InvocationRecordingWrapper.java @@ -0,0 +1,208 @@ +/* + * 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.common.test; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; + +import ch.systemsx.cisd.common.shared.basic.string.CommaSeparatedListBuilder; + +/** + * Helper class which allows to record method invocations. + * + * @author Franz-Josef Elmer + */ +public class InvocationRecordingWrapper<T> +{ + /** + * Creates an instance which contains a proxy of the specified object. + * + * @param type Interface the specified object implements. The proxy will implement the same + * interface. + * @param returnTypesToWrap Interfaces return values of invocations might implement. Invocations + * on such return values will also be recorded. This is done recursively. If the + * return value is an array or a list of such types the elements of the returned + * array/list will also be wrapped. + */ + @SuppressWarnings("unchecked") + public static <T> InvocationRecordingWrapper<T> wrap(final T object, Class<T> type, + final Class<?>... returnTypesToWrap) + { + final InvocationRecordingWrapper<T> wrapper = new InvocationRecordingWrapper<T>(); + wrapper.proxy = (T) createProxy(wrapper, null, object, type, returnTypesToWrap); + return wrapper; + } + + private static Object createProxy(final InvocationRecordingWrapper<?> wrapper, + final String prefix, final Object object, Class<?> type, + final Class<?>... returnTypesToWrap) + { + return Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[] + { type }, new InvocationHandler() + { + @Override + public Object invoke(Object obj, Method method, Object[] parameters) + throws Throwable + { + String record = wrapper.record(prefix, method, parameters); + Object returnValue = method.invoke(object, parameters); + if (returnValue != null) + { + Class<?> returnValueClass = returnValue.getClass(); + Class<?> matchingClass = + tryGetMatchingClass(returnValueClass, returnTypesToWrap); + if (matchingClass != null) + { + return createProxy(wrapper, record, returnValue, matchingClass, + returnTypesToWrap); + } + if (returnValueClass.isArray()) + { + return handleArray(wrapper, record, returnValue, returnValueClass, + returnTypesToWrap); + } + if (returnValue instanceof List) + { + return handleList(wrapper, record, returnValue, returnTypesToWrap); + } + } + return returnValue; + } + }); + } + + private static Object handleList(final InvocationRecordingWrapper<?> wrapper, String prefix, + Object returnValue, final Class<?>... returnTypesToWrap) + { + List<?> returnList = (List<?>) returnValue; + if (returnList.isEmpty()) + { + return returnValue; + } + List<Object> result = new ArrayList<Object>(returnList.size()); + for (int i = 0; i < returnList.size(); i++) + { + Object element = returnList.get(i); + Class<?> mc = tryGetMatchingClass(element.getClass(), returnTypesToWrap); + if (mc == null) + { + result.add(element); + } else + { + result.add(createProxy(wrapper, prefix + ".get(" + i + ")", element, mc, + returnTypesToWrap)); + } + } + return result; + } + + private static Object handleArray(final InvocationRecordingWrapper<?> wrapper, String prefix, + Object returnValue, Class<?> returnValueClass, final Class<?>... returnTypesToWrap) + { + Class<?> componentType = returnValueClass.getComponentType(); + Class<?> clazz = tryGetMatchingClass(componentType, returnTypesToWrap); + if (clazz == null) + { + return returnValue; + } + int size = Array.getLength(returnValue); + Object newArray = Array.newInstance(componentType, size); + for (int i = 0; i < size; i++) + { + Array.set( + newArray, + i, + createProxy(wrapper, prefix + "[" + i + "]", Array.get(returnValue, i), clazz, + returnTypesToWrap)); + } + return newArray; + } + + private static Class<?> tryGetMatchingClass(Class<?> clazz, Class<?>... classes) + { + for (Class<?> c : classes) + { + if (c.isAssignableFrom(clazz)) + { + return c; + } + } + return null; + } + + private List<String> records = new ArrayList<String>(); + + private T proxy; + + private InvocationRecordingWrapper() + { + } + + /** + * Returns the proxy of the original object. + */ + public T getProxy() + { + return proxy; + } + + /** + * Returns all recorded invocations as a list. + */ + public List<String> getRecords() + { + return records; + } + + /** + * Returns all recorded invocations as a multi-line text where each line contains an invocation. + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + for (String record : records) + { + if (builder.length() > 0) + { + builder.append("\n"); + } + builder.append(record); + } + return builder.toString(); + } + + private String record(String prefix, Method method, Object[] parameters) + { + CommaSeparatedListBuilder builder = new CommaSeparatedListBuilder(); + if (parameters != null) + { + for (Object parameter : parameters) + { + builder.append(parameter); + } + } + String record = + (prefix == null ? "" : prefix + ".") + method.getName() + "(" + builder + ")"; + records.add(record); + return record; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingExecutor.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingExecutor.java index ec0ff72cc8dc2ca5703c87aba4e9b9fa2eef92cd..d679681db98ca6b8cff0668ea00684b959fa00fe 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingExecutor.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingExecutor.java @@ -29,7 +29,7 @@ import ch.systemsx.cisd.common.logging.ISimpleLogger; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.logging.LogLevel; -import ch.systemsx.cisd.etlserver.plugins.AutoArchiverTask; +import ch.systemsx.cisd.common.string.Template; import ch.systemsx.cisd.openbis.dss.generic.shared.ArchiverTaskContext; import ch.systemsx.cisd.openbis.dss.generic.shared.IArchiverPlugin; import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider; @@ -67,12 +67,16 @@ class ArchivingExecutor implements IPostRegistrationTaskExecutor private final boolean updateStatus; - ArchivingExecutor(String dataSetCode, boolean updateStatus, IEncapsulatedOpenBISService service, - IArchiverPlugin archiver, IDataSetDirectoryProvider dataSetDirectoryProvider, + private Template notificationTemplate; + + ArchivingExecutor(String dataSetCode, boolean updateStatus, Template notificationTemplate, + IEncapsulatedOpenBISService service, IArchiverPlugin archiver, + IDataSetDirectoryProvider dataSetDirectoryProvider, IHierarchicalContentProvider hierarchicalContentProvider) { this.dataSetCode = dataSetCode; this.updateStatus = updateStatus; + this.notificationTemplate = notificationTemplate; this.service = service; this.archiver = archiver; this.dataSetDirectoryProvider = dataSetDirectoryProvider; @@ -120,32 +124,30 @@ class ArchivingExecutor implements IPostRegistrationTaskExecutor ProcessingStatus processingStatus = archiver.archive(dataSetAsList, context, false); if (false == processingStatus.getErrorStatuses().isEmpty()) { - notifyAdministrator(processingStatus); + notifyAdministrator(processingStatus, notificationTemplate.createFreshCopy()); + } + if (updateStatus) + { + service.compareAndSetDataSetStatus(dataSetCode, BACKUP_PENDING, AVAILABLE, true); } - service.compareAndSetDataSetStatus(dataSetCode, BACKUP_PENDING, AVAILABLE, true); } } - private void notifyAdministrator(ProcessingStatus processingStatus) + private void notifyAdministrator(ProcessingStatus processingStatus, Template template) { - StringBuilder message = new StringBuilder(); - String failedMessage = - String.format("Eager archiving of dataset '%s' has failed. \n", dataSetCode); - message.append(failedMessage); + template.bind("dataSet", dataSetCode); + StringBuilder builder = new StringBuilder(); for (Status status : processingStatus.getErrorStatuses()) { if (status.tryGetErrorMessage() != null) { - message.append("Error encountered : " + status.tryGetErrorMessage()); - message.append("\n"); + builder.append("Error encountered : " + status.tryGetErrorMessage()); + builder.append("\n"); } } - String footer = - String.format("If you wish to archive the dataset in the future, " - + "you can configure an '%s'.", AutoArchiverTask.class.getSimpleName()); - message.append(footer); + template.bind("errors", builder.toString()); - notificationLog.error(message); + notificationLog.error(template.createText()); } @Override diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingPostRegistrationTask.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingPostRegistrationTask.java index e48f26b4f5f1fd99d2146ccc26686436abde6f52..a28b7d18957d463069697bb0f5309ca55f7865c1 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingPostRegistrationTask.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/ArchivingPostRegistrationTask.java @@ -19,6 +19,8 @@ package ch.systemsx.cisd.etlserver.postregistration; import java.util.Properties; +import ch.systemsx.cisd.common.string.Template; +import ch.systemsx.cisd.etlserver.plugins.AutoArchiverTask; import ch.systemsx.cisd.openbis.dss.generic.shared.IArchiverPlugin; import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.IDataStoreServiceInternal; @@ -47,8 +49,12 @@ public class ArchivingPostRegistrationTask extends AbstractPostRegistrationTaskF dataStoreService.getDataSetDirectoryProvider(); IHierarchicalContentProvider hierarchicalContentProvider = ServiceProvider.getHierarchicalContentProvider(); - return new ArchivingExecutor(dataSetCode, true, service, archiver, dataSetDirectoryProvider, - hierarchicalContentProvider); + Template notificationTemplate = + new Template("Eager archiving of dataset '${dataSet}' has failed.\n${errors}\n" + + "If you wish to archive the dataset in the future, " + + "you can configure an '" + AutoArchiverTask.class.getSimpleName() + "'."); + return new ArchivingExecutor(dataSetCode, true, notificationTemplate, service, archiver, + dataSetDirectoryProvider, hierarchicalContentProvider); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTask.java b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTask.java index 2db42b3db1b4737f87a9df102014d92f9aa1925a..5a73dfaa7fa9cd0b107ce65c890ab6ed2fec49a9 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTask.java +++ b/datastore_server/source/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTask.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Properties; import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.string.Template; import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.AbstractArchiverProcessingPlugin; import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.DataSetFileOperationsManager; import ch.systemsx.cisd.openbis.dss.generic.server.plugins.standard.IDataSetFileOperationsManager; @@ -33,6 +34,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetStatusUpdater; import ch.systemsx.cisd.openbis.dss.generic.shared.IDataStoreServiceInternal; 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.generic.shared.basic.dto.DataSetArchivingStatus; @@ -48,6 +50,7 @@ public class SecondCopyPostRegistrationTask extends AbstractPostRegistrationTask private final IHierarchicalContentProvider hierarchicalContentProvider; private final IDataSetDirectoryProvider dataSetDirectoryProvider; private final IArchiverPlugin archiver; + private final Template notificationTemplate; public SecondCopyPostRegistrationTask(Properties properties, IEncapsulatedOpenBISService service) { @@ -71,13 +74,18 @@ public class SecondCopyPostRegistrationTask extends AbstractPostRegistrationTask dataSetDirectoryProvider = dataStoreService.getDataSetDirectoryProvider(); File storeRoot = dataSetDirectoryProvider.getStoreRoot(); properties.setProperty(AbstractArchiverProcessingPlugin.SYNCHRONIZE_ARCHIVE, "false"); - archiver = new Archiver(properties, storeRoot, fileOperationManager); + notificationTemplate = + new Template( + "Creating a second copy of dataset '${dataSet}' has failed.\n${errors}"); + archiver = + new Archiver(properties, storeRoot, service, fileOperationManager, dataStoreService + .getDataSetDirectoryProvider().getShareIdManager()); } @Override public IPostRegistrationTaskExecutor createExecutor(String dataSetCode) { - return new ArchivingExecutor(dataSetCode, false, service, archiver, dataSetDirectoryProvider, + return new ArchivingExecutor(dataSetCode, false, notificationTemplate, service, archiver, dataSetDirectoryProvider, hierarchicalContentProvider); } @@ -86,11 +94,12 @@ public class SecondCopyPostRegistrationTask extends AbstractPostRegistrationTask private static final long serialVersionUID = 1L; - Archiver(Properties properties, File storeRoot, - IDataSetFileOperationsManager fileOperationsManager) + Archiver(Properties properties, File storeRoot, IEncapsulatedOpenBISService service, + IDataSetFileOperationsManager fileOperationsManager, IShareIdManager shareIdManager) { super(properties, storeRoot, fileOperationsManager, RsyncArchiver.DeleteAction.DELETE, ChecksumVerificationCondition.IF_AVAILABLE); + setService(service); setStatusUpdater(new IDataSetStatusUpdater() { @Override @@ -99,6 +108,7 @@ public class SecondCopyPostRegistrationTask extends AbstractPostRegistrationTask { } }); + setShareIdManager(shareIdManager); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java index 504926852e11d679f708045bc3632fc841bd7f1e..014772bf61fcf6b641c86c95fd635f605dc51b00 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractArchiverProcessingPlugin.java @@ -547,10 +547,20 @@ public abstract class AbstractArchiverProcessingPlugin extends AbstractDatastore return service; } - public void setStatusUpdater(IDataSetStatusUpdater statusUpdater) + protected void setStatusUpdater(IDataSetStatusUpdater statusUpdater) { this.statusUpdater = statusUpdater; } + + protected void setShareIdManager(IShareIdManager shareIdManager) + { + this.shareIdManager = shareIdManager; + } + + protected void setService(IEncapsulatedOpenBISService service) + { + this.service = service; + } /** * @author Franz-Josef Elmer 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 c5214a04721ab31dba94f02f9defca42353e66bc..72fca00e0c3c094e8b7b6b438a5cc257daf4331a 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 @@ -186,9 +186,9 @@ public class RsyncArchiver extends AbstractArchiverProcessingPlugin new File(context.getDirectoryProvider().getStoreRoot(), STAGING_FOLDER + "/" + dataSetCode); temp.mkdirs(); + IHierarchicalContent archivedContent = null; try { - IHierarchicalContent archivedContent; // We want to perform the check if the archived content is correct // (filesizes/checksums) // For this we want to have the archived content locally. If it is not available @@ -214,6 +214,11 @@ public class RsyncArchiver extends AbstractArchiverProcessingPlugin status = checkHierarchySizeAndChecksums(root, archivedRoot, checksumVerificationCondition); } finally { + content.close(); + if (archivedContent != null) + { + archivedContent.close(); + } FileUtils.deleteQuietly(temp); } } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTaskTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTaskTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8d15acd6e433a7e2d3bf3bfb1d22fe939bff5f2b --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/etlserver/postregistration/SecondCopyPostRegistrationTaskTest.java @@ -0,0 +1,277 @@ +/* + * 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.etlserver.postregistration; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Properties; + +import org.apache.log4j.Level; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.logging.BufferedAppender; +import ch.systemsx.cisd.common.logging.LogUtils; +import ch.systemsx.cisd.common.test.InvocationRecordingWrapper; +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.common.io.hierarchical_content.api.IHierarchicalContentNode; +import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetDirectoryProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.HierarchicalContentProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.IConfigProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.IDataSetDirectoryProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.IDataStoreServiceInternal; +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.ServiceProviderTestWrapper; +import ch.systemsx.cisd.openbis.dss.generic.shared.content.IContentCache; +import ch.systemsx.cisd.openbis.dss.generic.shared.content.IDssServiceRpcGenericFactory; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalDataLocationNode; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PhysicalDataSet; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataSetBuilder; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataStoreBuilder; +import ch.systemsx.cisd.openbis.generic.shared.dto.OpenBISSessionHolder; + +/** + * @author Franz-Josef Elmer + */ +public class SecondCopyPostRegistrationTaskTest extends AbstractFileSystemTestCase +{ + private static final String DATA_STORE_CODE = "DSS"; + + private static final String EXAMPLE_CONTENT = "Hello world!"; + + private static final String DATA_SET1_LOCATION = "a/b/c/ds1"; + + private static final String DATA_SET1_EXAMPLE_FILE_PATH = DATA_SET1_LOCATION + + "/original/hello.txt"; + + private static final String DATA_SET1 = "ds1"; + + private static final String SHARE_ID = "1"; + + private BufferedAppender logRecorder; + + private Mockery context; + + private IEncapsulatedOpenBISService service; + + private IDataStoreServiceInternal dataStoreService; + + private File destination; + + private SecondCopyPostRegistrationTask task; + + private File store; + + private IShareIdManager shareIdManager; + + private IConfigProvider configProvider; + + private IContentCache contentCache; + + private InvocationRecordingWrapper<IHierarchicalContentProvider> contentProviderRecordingWrapper; + + @BeforeMethod + public void beforeMethod() + { + logRecorder = new BufferedAppender("%-5p %c - %m%n", Level.INFO); + logRecorder.addFilter(new Filter() + { + @Override + public int decide(LoggingEvent event) + { + String loggerName = event.getLoggerName(); + return loggerName.contains("RsyncCopier") ? Filter.DENY : Filter.ACCEPT; + } + }); + context = new Mockery(); + service = context.mock(IEncapsulatedOpenBISService.class); + dataStoreService = context.mock(IDataStoreServiceInternal.class); + shareIdManager = context.mock(IShareIdManager.class); + configProvider = context.mock(IConfigProvider.class); + contentCache = context.mock(IContentCache.class); + IDssServiceRpcGenericFactory dssServiceFactory = + context.mock(IDssServiceRpcGenericFactory.class); + store = new File(workingDirectory, "store"); + final IDataSetDirectoryProvider dirProvider = + new DataSetDirectoryProvider(store, shareIdManager); + context.checking(new Expectations() + { + { + allowing(dataStoreService).getDataSetDirectoryProvider(); + will(returnValue(dirProvider)); + + allowing(configProvider).getStoreRoot(); + will(returnValue(store)); + + allowing(configProvider).getDataStoreCode(); + will(returnValue(DATA_STORE_CODE)); + } + }); + OpenBISSessionHolder sessionHolder = new OpenBISSessionHolder(); + sessionHolder.setDataStoreCode(DATA_STORE_CODE); + contentProviderRecordingWrapper = InvocationRecordingWrapper.<IHierarchicalContentProvider>wrap( + new HierarchicalContentProvider(service, shareIdManager, configProvider, + contentCache, new DefaultFileBasedHierarchicalContentFactory(), + dssServiceFactory, sessionHolder, null), IHierarchicalContentProvider.class, IHierarchicalContent.class, IHierarchicalContentNode.class); + File exampleFile = new File(store, SHARE_ID + "/" + DATA_SET1_EXAMPLE_FILE_PATH); + exampleFile.getParentFile().mkdirs(); + FileUtilities.writeToFile(exampleFile, EXAMPLE_CONTENT); + destination = new File(workingDirectory, "second-copy-destination"); + Properties properties = new Properties(); + properties.setProperty("destination", destination.getAbsolutePath()); + task = + new SecondCopyPostRegistrationTask(properties, service, dataStoreService, + contentProviderRecordingWrapper.getProxy()); + } + + @AfterMethod(alwaysRun = true) + public void afterMethod() + { + ServiceProviderTestWrapper.restoreApplicationContext(); + if (logRecorder != null) + { + logRecorder.reset(); + } + if (context != null) + { + // The following line of code should also be called at the end of each test method. + // Otherwise one do not known which test failed. + context.assertIsSatisfied(); + } + } + + @Test + public void testHappyCase() + { + context.checking(new Expectations() + { + { + one(service).listDataSetsByCode(Arrays.asList(DATA_SET1)); + PhysicalDataSet ds1 = + new DataSetBuilder(42).code(DATA_SET1) + .store(new DataStoreBuilder(DATA_STORE_CODE).getStore()) + .fileFormat("TXT").location(DATA_SET1_LOCATION).getDataSet(); + will(returnValue(Arrays.asList(ds1))); + + one(service).tryGetDataSetLocation(DATA_SET1); + will(returnValue(new ExternalDataLocationNode(ds1))); + + allowing(shareIdManager).getShareId(DATA_SET1); + will(returnValue(SHARE_ID)); + + one(shareIdManager).lock(DATA_SET1); + one(shareIdManager).releaseLock(DATA_SET1); + + one(service) + .updateShareIdAndSize(DATA_SET1, SHARE_ID, EXAMPLE_CONTENT.length()); + } + }); + + IPostRegistrationTaskExecutor executor = task.createExecutor(DATA_SET1, false); + executor.execute(); + + assertEquals( + "INFO OPERATION.AbstractDatastorePlugin - Archiving of the " + + "following datasets has been requested: [Dataset 'ds1']\n" + + "INFO OPERATION.DataSetFileOperationsManager - " + + "Copy dataset 'ds1' from '" + store.getPath() + "/1/a/b/c/ds1' to '" + + destination.getAbsolutePath() + "/a/b/c", logRecorder.getLogContent()); + assertEquals( + EXAMPLE_CONTENT, + FileUtilities.loadToString( + new File(store, SHARE_ID + "/" + DATA_SET1_EXAMPLE_FILE_PATH)).trim()); + assertEquals(EXAMPLE_CONTENT, + FileUtilities.loadToString(new File(destination, DATA_SET1_EXAMPLE_FILE_PATH)) + .trim()); + assertEquals( + "asContent(ds1)\n" + + "asContent(ds1).getRootNode()\n" + + "asContent(ds1).getRootNode().getRelativePath()\n" + + "asContent(ds1).getRootNode().isDirectory()\n" + + "asContent(ds1).getRootNode().getChildNodes()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getRelativePath()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).isDirectory()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getChildNodes()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getChildNodes().get(0).getRelativePath()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getChildNodes().get(0).isDirectory()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getChildNodes().get(0).getFileLength()\n" + + "asContent(ds1).getRootNode().getChildNodes().get(0).getChildNodes().get(0).isChecksumCRC32Precalculated()\n" + + "asContent(ds1).close()", contentProviderRecordingWrapper.toString()); + context.assertIsSatisfied(); + } + + @Test + public void testDestinationIsNotADirectory() throws IOException + { + context.checking(new Expectations() + { + { + one(service).listDataSetsByCode(Arrays.asList(DATA_SET1)); + PhysicalDataSet ds1 = + new DataSetBuilder(42).code(DATA_SET1) + .store(new DataStoreBuilder(DATA_STORE_CODE).getStore()) + .fileFormat("TXT").location(DATA_SET1_LOCATION).getDataSet(); + will(returnValue(Arrays.asList(ds1))); + + allowing(shareIdManager).getShareId(DATA_SET1); + will(returnValue(SHARE_ID)); + + one(service) + .updateShareIdAndSize(DATA_SET1, SHARE_ID, EXAMPLE_CONTENT.length()); + } + }); + + destination.createNewFile(); + IPostRegistrationTaskExecutor executor = task.createExecutor(DATA_SET1, false); + executor.execute(); + + assertEquals("INFO OPERATION.AbstractDatastorePlugin - Archiving of the " + + "following datasets has been requested: [Dataset 'ds1']\n" + + "INFO OPERATION.DataSetFileOperationsManager - Copy dataset 'ds1' from '" + + store.getPath() + "/1/a/b/c/ds1' to '" + destination.getAbsolutePath() + + "/a/b/c\n" + "ERROR OPERATION.AbstractDatastorePlugin - Archiving failed :path '" + + destination.getAbsolutePath() + "/a/b/c' does not exist\n" + + "java.io.IOException: path '" + destination.getAbsolutePath() + + "/a/b/c' does not exist\n" + "ERROR OPERATION.AbstractDatastorePlugin - " + + "Archiving for dataset ds1 finished with the status: " + + "ERROR: \"Archiving failed :path '" + destination.getAbsolutePath() + + "/a/b/c' does not exist\".\n" + "ERROR NOTIFY.ArchivingExecutor - " + + "Creating a second copy of dataset 'ds1' has failed.\n" + + "Error encountered : Archiving failed :path '" + destination.getAbsolutePath() + + "/a/b/c' does not exist", + LogUtils.removeEmbeddedStackTrace(logRecorder.getLogContent())); + assertEquals( + EXAMPLE_CONTENT, + FileUtilities.loadToString( + new File(store, SHARE_ID + "/" + DATA_SET1_EXAMPLE_FILE_PATH)).trim()); + assertEquals(false, new File(destination, DATA_SET1_EXAMPLE_FILE_PATH).exists()); + assertEquals("", contentProviderRecordingWrapper.toString()); + context.assertIsSatisfied(); + } + +}