From 93414e94f3958361058a852d187cb66f5241eb1c Mon Sep 17 00:00:00 2001 From: felmer <felmer> Date: Tue, 4 Oct 2011 13:53:02 +0000 Subject: [PATCH] LMS-2558 implemented and tested SVN: 23177 --- .../server/EncapsulatedOpenBISService.java | 22 +- .../generic/server/ftp/DSSFileSystemView.java | 12 +- .../generic/server/ftp/FtpFileFactory.java | 8 +- .../server/ftp/FtpPathResolverContext.java | 11 +- .../dss/generic/server/ftp/FtpServer.java | 11 +- .../generic/server/ftp/FtpServerConfig.java | 13 +- .../server/ftp/resolver/AbstractFtpFile.java | 3 +- .../resolver/FtpFileEvaluationContext.java | 11 +- .../server/ftp/resolver/FtpFileImpl.java | 21 +- .../TemplateBasedDataSetResourceResolver.java | 269 +++++++-- .../dss/generic/shared/ServiceProvider.java | 6 + .../source/java/dssApplicationContext.xml | 7 + .../server/ftp/FtpServerConfigBuilder.java | 11 +- ...plateBasedDataSetResourceResolverTest.java | 518 ++++++++++-------- 14 files changed, 623 insertions(+), 300 deletions(-) diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java index 6ee9df81701..35ef23f3489 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/EncapsulatedOpenBISService.java @@ -24,6 +24,7 @@ import org.apache.commons.lang.time.DateUtils; import org.apache.log4j.Logger; import org.springframework.beans.factory.FactoryBean; +import ch.systemsx.cisd.common.api.client.ServiceFinder; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; @@ -34,6 +35,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; import ch.systemsx.cisd.openbis.dss.generic.shared.dto.DataSetInformation; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; import ch.systemsx.cisd.openbis.generic.shared.ResourceNames; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; import ch.systemsx.cisd.openbis.generic.shared.api.v1.OpenBisServiceFactory; import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; @@ -104,9 +106,27 @@ public final class EncapsulatedOpenBISService implements IEncapsulatedOpenBISSer { return factory.createService(); } - return factory.createService(Integer.parseInt(timeout) * DateUtils.MILLIS_PER_MINUTE); + return factory.createService(normalizeTimeout(timeout)); } + /** + * Creates a remote version of {@link IGeneralInformationService} for specified URL and + * time out (in minutes). + */ + public static IGeneralInformationService createGeneralInformationService(String openBISURL, + String timeout) + { + ServiceFinder generalInformationServiceFinder = + new ServiceFinder("openbis", IGeneralInformationService.SERVICE_URL); + return generalInformationServiceFinder.createService(IGeneralInformationService.class, + openBISURL, normalizeTimeout(timeout)); + } + + private static long normalizeTimeout(String timeout) + { + return Integer.parseInt(timeout) * DateUtils.MILLIS_PER_MINUTE; + } + public EncapsulatedOpenBISService(IETLLIMSService service, OpenBISSessionHolder sessionHolder) { this(service, sessionHolder, null); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/DSSFileSystemView.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/DSSFileSystemView.java index a622aac2e94..709360c9636 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/DSSFileSystemView.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/DSSFileSystemView.java @@ -25,6 +25,7 @@ import ch.systemsx.cisd.cifex.client.application.utils.StringUtils; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; /** * A central class that manages the movement of a user up and down the exposed hierarchical @@ -41,15 +42,19 @@ public class DSSFileSystemView implements FileSystemView private final IETLLIMSService service; + private final IGeneralInformationService generalInfoService; + private FtpFile workingDirectory; private final IFtpPathResolverRegistry pathResolverRegistry; DSSFileSystemView(String sessionToken, IETLLIMSService service, + IGeneralInformationService generalInfoService, IFtpPathResolverRegistry pathResolverRegistry) throws FtpException { this.sessionToken = sessionToken; this.service = service; + this.generalInfoService = generalInfoService; this.pathResolverRegistry = pathResolverRegistry; this.workingDirectory = getHomeDirectory(); } @@ -82,15 +87,16 @@ public class DSSFileSystemView implements FileSystemView try { FtpPathResolverContext context = - new FtpPathResolverContext(sessionToken, service, pathResolverRegistry); + new FtpPathResolverContext(sessionToken, service, generalInfoService, + pathResolverRegistry); return pathResolverRegistry.tryResolve(normalizedPath, context); } catch (RuntimeException rex) { String message = - String.format("Error while resolving FTP path '%s' : '%s", path, + String.format("Error while resolving FTP path '%s' : %s", path, rex.getMessage()); operationLog.error(message); - return null; + throw new FtpException(message, rex); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java index ef4f53367d2..aa8f4ed3822 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java @@ -21,6 +21,7 @@ import java.io.File; import org.apache.ftpserver.ftplet.FtpFile; import ch.systemsx.cisd.common.io.hierarchical_content.IHierarchicalContentNodeFilter; +import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver.FtpFileImpl; @@ -41,11 +42,12 @@ public class FtpFileFactory * {@link IHierarchicalContentNode} on their own. */ public static FtpFile createFtpFile(String dataSetCode, String path, - IHierarchicalContentNode contentNode, IHierarchicalContentNodeFilter childrenFilter) + IHierarchicalContentNode contentNode, IHierarchicalContent content, + IHierarchicalContentNodeFilter childrenFilter) { return new FtpFileImpl(dataSetCode, path, contentNode.getRelativePath(), - contentNode.isDirectory(), getSize(contentNode), - getLastModified(contentNode), childrenFilter); + contentNode.isDirectory(), getSize(contentNode), getLastModified(contentNode), + content, childrenFilter); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverContext.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverContext.java index 465e4cce345..17d97ea2384 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverContext.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverContext.java @@ -17,6 +17,7 @@ package ch.systemsx.cisd.openbis.dss.generic.server.ftp; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; /** * An object holding all necessary context information for ftp path resolution. @@ -30,13 +31,16 @@ public class FtpPathResolverContext private final IETLLIMSService service; + private final IGeneralInformationService generalInfoService; + private final IFtpPathResolverRegistry resolverRegistry; - public FtpPathResolverContext(String sessionToken, IETLLIMSService service, + public FtpPathResolverContext(String sessionToken, IETLLIMSService service, IGeneralInformationService generalInfoService, IFtpPathResolverRegistry resolverRegistry) { this.sessionToken = sessionToken; this.service = service; + this.generalInfoService = generalInfoService; this.resolverRegistry = resolverRegistry; } @@ -50,6 +54,11 @@ public class FtpPathResolverContext return service; } + public IGeneralInformationService getGeneralInfoService() + { + return generalInfoService; + } + public IFtpPathResolverRegistry getResolverRegistry() { return resolverRegistry; diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServer.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServer.java index 2ee047d6906..963b13e8840 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServer.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServer.java @@ -33,6 +33,7 @@ import org.apache.log4j.Logger; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; /** * Controls the lifecycle of an FTP server built into DSS. @@ -54,10 +55,13 @@ public class FtpServer implements FileSystemFactory private org.apache.ftpserver.FtpServer server; - public FtpServer(IETLLIMSService openBisService, UserManager userManager, Properties configProps) - throws Exception + private final IGeneralInformationService generalInfoService; + + public FtpServer(IETLLIMSService openBisService, IGeneralInformationService generalInfoService, + UserManager userManager, Properties configProps) throws Exception { this.openBisService = openBisService; + this.generalInfoService = generalInfoService; this.userManager = userManager; this.config = new FtpServerConfig(configProps); this.pathResolverRegistry = new FtpPathResolverRegistry(config); @@ -128,7 +132,8 @@ public class FtpServer implements FileSystemFactory if (user instanceof FtpUser) { String sessionToken = ((FtpUser) user).getSessionToken(); - return new DSSFileSystemView(sessionToken, openBisService, pathResolverRegistry); + return new DSSFileSystemView(sessionToken, openBisService, generalInfoService, + pathResolverRegistry); } else { throw new FtpException("Unsupported user type."); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfig.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfig.java index 2347066e464..a77ead83ca3 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfig.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfig.java @@ -29,6 +29,7 @@ import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.utilities.ExtendedProperties; import ch.systemsx.cisd.common.utilities.PropertyUtils; +import ch.systemsx.cisd.common.utilities.Template; import ch.systemsx.cisd.openbis.dss.generic.server.ConfigParameters; /** @@ -62,6 +63,8 @@ public class FtpServerConfig final static String ACTIVE_PORT_KEY = PREFIX + "activemode.port"; final static String PASSIVE_MODE_PORT_RANGE_KEY = PREFIX + "passivemode.port.range"; + + final static String SHOW_PARENTS_AND_CHILDREN_KEY = PREFIX + "show-parents-and-children"; private static final int DEFAULT_PORT = 2121; @@ -107,6 +110,8 @@ public class FtpServerConfig private Map<String /* dataset type */, String /* filter pattern */> fileListFilters = new HashMap<String, String>(); + private boolean showParentsAndChildren; + public FtpServerConfig(Properties props) { this.startServer = PropertyUtils.getBoolean(props, ENABLE_KEY, false); if (startServer) @@ -131,7 +136,8 @@ public class FtpServerConfig maxThreads = PropertyUtils.getPosInt(props, MAX_THREADS_KEY, DEFAULT_MAX_THREADS); dataSetDisplayTemplate = PropertyUtils.getProperty(props, DATASET_DISPLAY_TEMPLATE_KEY, DEFAULT_DATASET_TEMPLATE); - + showParentsAndChildren = PropertyUtils.getBoolean(props, SHOW_PARENTS_AND_CHILDREN_KEY, false); + ExtendedProperties fileListSubPathProps = ExtendedProperties.getSubset(props, DATASET_FILELIST_SUBPATH_KEY, true); for (Object key : fileListSubPathProps.keySet()) @@ -211,6 +217,11 @@ public class FtpServerConfig return dataSetDisplayTemplate; } + public boolean isShowParentsAndChildren() + { + return showParentsAndChildren; + } + public Map<String, String> getFileListSubPaths() { return Collections.unmodifiableMap(fileListSubPaths); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/AbstractFtpFile.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/AbstractFtpFile.java index 8f4896b2b48..2987e797d93 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/AbstractFtpFile.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/AbstractFtpFile.java @@ -19,7 +19,6 @@ package ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver; import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.util.Collections; import java.util.List; import org.apache.ftpserver.ftplet.FtpFile; @@ -60,7 +59,7 @@ public abstract class AbstractFtpFile implements FtpFile } catch (RuntimeException rex) { operationLog.error("Error while listing files for FTP :" + rex.getMessage(), rex); - return Collections.emptyList(); + throw rex; } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java index 35363311043..e112df1a69b 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java @@ -28,7 +28,6 @@ import org.apache.commons.lang.StringUtils; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode; import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; -import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData; /** @@ -55,7 +54,14 @@ public class FtpFileEvaluationContext private Map<String /* dataset code */, IHierarchicalContent> contents = new HashMap<String, IHierarchicalContent>(); + private IHierarchicalContentProvider contentProvider; + private List<EvaluatedElement> evaluatedPaths = new ArrayList<EvaluatedElement>(); + + FtpFileEvaluationContext(IHierarchicalContentProvider contentProvider) + { + this.contentProvider = contentProvider; + } /** * @return the evaluation result. @@ -100,8 +106,7 @@ public class FtpFileEvaluationContext private IHierarchicalContent createHierarchicalContent(ExternalData dataSet) { - IHierarchicalContentProvider provider = ServiceProvider.getHierarchicalContentProvider(); - return provider.asContent(dataSet); + return contentProvider.asContent(dataSet); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java index 13ce96597c6..3283f9613dc 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java @@ -53,8 +53,10 @@ public class FtpFileImpl extends AbstractFtpFile private final IHierarchicalContentNodeFilter childrenFilter; + private IHierarchicalContent content; + public FtpFileImpl(String dataSetCode, String path, String pathInDataSet, boolean isDirectory, - long size, long lastModified, IHierarchicalContentNodeFilter childrenFilter) + long size, long lastModified, IHierarchicalContent content, IHierarchicalContentNodeFilter childrenFilter) { super(path); this.dataSetCode = dataSetCode; @@ -62,15 +64,15 @@ public class FtpFileImpl extends AbstractFtpFile this.isDirectory = isDirectory; this.size = size; this.lastModified = lastModified; + this.content = content; this.childrenFilter = childrenFilter; } public InputStream createInputStream(long offset) throws IOException { - IHierarchicalContent content = createHierarchicalContent(); try { - IHierarchicalContentNode contentNode = getContentNodeForThisFile(content); + IHierarchicalContentNode contentNode = getContentNodeForThisFile(); InputStream result = HierarchicalContentUtils.getInputStreamAutoClosingContent(contentNode, content); @@ -122,10 +124,9 @@ public class FtpFileImpl extends AbstractFtpFile throw new UnsupportedOperationException(); } - IHierarchicalContent content = createHierarchicalContent(); try { - IHierarchicalContentNode contentNode = getContentNodeForThisFile(content); + IHierarchicalContentNode contentNode = getContentNodeForThisFile(); List<IHierarchicalContentNode> children = contentNode.getChildNodes(); List<org.apache.ftpserver.ftplet.FtpFile> result = new ArrayList<org.apache.ftpserver.ftplet.FtpFile>(); @@ -137,7 +138,7 @@ public class FtpFileImpl extends AbstractFtpFile String childPath = absolutePath + FtpConstants.FILE_SEPARATOR + childNode.getName(); FtpFile childFile = - FtpFileFactory.createFtpFile(dataSetCode, childPath, childNode, + FtpFileFactory.createFtpFile(dataSetCode, childPath, childNode, content, childrenFilter); result.add(childFile); } @@ -151,10 +152,14 @@ public class FtpFileImpl extends AbstractFtpFile private IHierarchicalContent createHierarchicalContent() { - return ServiceProvider.getHierarchicalContentProvider().asContent(dataSetCode); + if (content == null) + { + content = ServiceProvider.getHierarchicalContentProvider().asContent(dataSetCode); + } + return content; } - private IHierarchicalContentNode getContentNodeForThisFile(IHierarchicalContent content) + private IHierarchicalContentNode getContentNodeForThisFile() { return content.getNode(pathInDataSet); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java index ae253df0aa7..8cf26609e20 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolver.java @@ -17,6 +17,7 @@ package ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -30,6 +31,8 @@ import org.apache.commons.lang.time.DateFormatUtils; import org.apache.ftpserver.ftplet.FtpFile; import org.apache.log4j.Logger; +import ch.rinn.restrictions.Private; +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; import ch.systemsx.cisd.common.io.hierarchical_content.IHierarchicalContentNodeFilter; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode; @@ -42,7 +45,11 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpServerConfig; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.IFtpPathResolver; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver.FtpFileEvaluationContext.EvaluatedElement; +import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData; @@ -51,8 +58,10 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifi /** * Resolves paths like - * "/<space-code>/<project-code>/<experiment-code>/<dataset-template>[/<sub-path>]*" to - * {@link FtpFile} objects. + * <pre> + * /<space-code>/<project-code>/<experiment-code>/<dataset-template>[/[PARENT-<dataset-template>|CHILD-<dataset-template>|<sub-path>]]* + * </pre> + * to {@link FtpFile} objects. * <p> * Subpaths are resolved as relative paths starting from the root of a dataset. * <p> @@ -74,8 +83,87 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, private static final String DATA_SET_DATE_FORMAT = "yyyy-MM-dd-HH-mm"; + private static final String PARENT_PREFIX = "PARENT-"; + + private static final String CHILD_PREFIX = "CHILD-"; + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, TemplateBasedDataSetResourceResolver.class); + + private final class DataSetFtpFolder extends AbstractFtpFolder + { + private final ExternalData dataSet; + + private final FtpPathResolverContext resolverContext; + + private DataSetFtpFolder(String absolutePath, ExternalData dataSet, + FtpPathResolverContext resolverContext) + { + super(absolutePath); + this.dataSet = dataSet; + this.resolverContext = resolverContext; + } + + @Override + public List<FtpFile> unsafeListFiles() throws RuntimeException + { + List<FtpFile> result = new ArrayList<FtpFile>(); + if (showParentsAndChildren) + { + IGeneralInformationService generalInfoService = + resolverContext.getGeneralInfoService(); + String sessionToken = resolverContext.getSessionToken(); + List<DataSet> dataSetsWithMetaData = + generalInfoService.getDataSetMetaData(sessionToken, + Arrays.asList(dataSet.getCode())); + + DataSet dataSetWithMetaData = dataSetsWithMetaData.get(0); + addNodesOfType(PARENT_PREFIX, result, dataSetWithMetaData.getParentCodes()); + addNodesOfType(CHILD_PREFIX, result, dataSetWithMetaData.getChildrenCodes()); + } + FtpFileEvaluationContext evalContext = createFtpFileEvaluationContext(); + try + { + IHierarchicalContent hierarchicalContent = + evalContext.getHierarchicalContent(dataSet); + IHierarchicalContentNode rootNode = + getDataSetFileListRoot(dataSet, hierarchicalContent); + List<IHierarchicalContentNode> childNodes = rootNode.getChildNodes(); + for (IHierarchicalContentNode childNode : childNodes) + { + IHierarchicalContentNodeFilter fileFilter = getFileFilter(dataSet); + if (fileFilter.accept(childNode)) + { + result.add(FtpFileFactory.createFtpFile(dataSet.getCode(), absolutePath + + FtpConstants.FILE_SEPARATOR + childNode.getName(), childNode, + hierarchicalContent, fileFilter)); + } + } + } finally + { + evalContext.close(); + } + return result; + } + + private void addNodesOfType(String prefix, List<FtpFile> result, List<String> dataSetCodes) + { + if (dataSetCodes.isEmpty()) + { + return; + } + String sessionToken = resolverContext.getSessionToken(); + List<ExternalData> dataSets = + resolverContext.getService().listDataSetsByCode(sessionToken, dataSetCodes); + for (int i = 0; i < dataSets.size(); i++) + { + ExternalData ds = dataSets.get(i); + String folderName = prefix + evaluateTemplate(ds, null, computeDisambiguation(i)); + result.add(new DataSetFtpFolder(absolutePath + FtpConstants.FILE_SEPARATOR + + folderName, ds, resolverContext)); + } + } + } /** * helper class holding the configuration properties for a data set type. @@ -99,18 +187,36 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, * @see #evaluateTemplate(ExternalData, String) to find out what variables are understood and * interpreted. */ - private final String template; + private final Template template; private final Map<String /* dataset type */, DataSetTypeConfig> dataSetTypeConfigs; private final DataSetTypeConfig defaultDSTypeConfig; + private boolean showParentsAndChildren; + + private boolean fileNamePresent; + + private IHierarchicalContentProvider contentProvider; + public TemplateBasedDataSetResourceResolver(FtpServerConfig ftpServerConfig) { - this.template = ftpServerConfig.getDataSetDisplayTemplate(); + this.template = new Template(ftpServerConfig.getDataSetDisplayTemplate()); + showParentsAndChildren = ftpServerConfig.isShowParentsAndChildren(); + fileNamePresent = template.getPlaceholderNames().contains(FILE_NAME_VARNAME); + if (fileNamePresent && showParentsAndChildren) + { + throw new ConfigurationFailureException( + "Template contains file name variable and " + + "the flag to show parents/children data sets is set."); + } this.dataSetTypeConfigs = initializeDataSetTypeConfigs(ftpServerConfig); this.defaultDSTypeConfig = new DataSetTypeConfig(); - + } + + void setContentProvider(IHierarchicalContentProvider contentProvider) + { + this.contentProvider = contentProvider; } /** @@ -122,23 +228,95 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return nestedLevels >= 4; } - public FtpFile resolve(String path, final FtpPathResolverContext resolverContext) + public FtpFile resolve(final String path, final FtpPathResolverContext resolverContext) { String experimentId = extractExperimentIdFromPath(path); - FtpFileEvaluationContext evalContext = - evaluateExperimentDataSets(experimentId, resolverContext); - if (evalContext == null) + IETLLIMSService service = resolverContext.getService(); + String sessionToken = resolverContext.getSessionToken(); + + Experiment experiment = tryGetExperiment(experimentId, service, sessionToken); + if (experiment == null) { - return null; + throw new IllegalArgumentException("Unknown experiment '" + experimentId + "'."); } + List<ExternalData> dataSets = + service.listDataSetsByExperimentID(sessionToken, new TechId(experiment)); + if (fileNamePresent) + { + FtpFileEvaluationContext evalContext = evaluateDataSetPaths(dataSets); + + try + { + return extractMatchingFileOrNull(path, experimentId, evalContext); + } finally + { + evalContext.close(); + } + } + return resolve(path, resolverContext, dataSets); + } - try + private FtpFile resolve(final String path, final FtpPathResolverContext resolverContext, + List<ExternalData> dataSets) + { + String[] pathElements = + StringUtils.splitByWholeSeparatorPreserveAllTokens(path, + FtpConstants.FILE_SEPARATOR); + FtpFile result = null; + for (int i = 4; i < pathElements.length; i++) { - return extractMatchingFileOrNull(path, experimentId, evalContext); - } finally + String dataSetPathElement = pathElements[i]; + if (result == null) + { + ExternalData dataSet = tryToFindDataSet(dataSets, dataSetPathElement); + if (dataSet == null) + { + throw createException(dataSetPathElement); + } + String subPath = + StringUtils.join(pathElements, FtpConstants.FILE_SEPARATOR, 0, i + 1); + result = new DataSetFtpFolder(subPath, dataSet, resolverContext); + } else + { + List<FtpFile> files = result.listFiles(); + FtpFile matchingFile = null; + for (FtpFile file : files) + { + if (dataSetPathElement.equals(file.getName())) + { + matchingFile = file; + break; + } + } + if (matchingFile == null) + { + throw createException(dataSetPathElement); + } + result = matchingFile; + } + } + return result; + } + + private IllegalArgumentException createException(String dataSetPathElement) + { + return new IllegalArgumentException("No match found for path element '" + + dataSetPathElement + "'."); + } + + private ExternalData tryToFindDataSet(List<ExternalData> dataSets, String dataSetPathElement) + { + for (int disambiguationIdx = 0; disambiguationIdx < dataSets.size(); disambiguationIdx++) { - evalContext.close(); + String disambiguationVar = computeDisambiguation(disambiguationIdx); + ExternalData dataSet = dataSets.get(disambiguationIdx); + String pathElement = evaluateTemplate(dataSet, null, disambiguationVar); + if (dataSetPathElement.equals(pathElement)) + { + return dataSet; + } } + return null; } private FtpFile extractMatchingFileOrNull(String path, String experimentId, @@ -157,7 +335,8 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, IHierarchicalContentNode contentNode = content.getNode(hierarchicalNodePath); if (fileFilter.accept(contentNode)) { - return FtpFileFactory.createFtpFile(dataSet.getCode(), path, contentNode, fileFilter); + return FtpFileFactory.createFtpFile(dataSet.getCode(), path, contentNode, content, + fileFilter); } else { return null; @@ -273,9 +452,14 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, { String childPath = parentPath + FtpConstants.FILE_SEPARATOR + evalElement.evaluatedTemplate; + String dataSetCode = evalElement.dataSet.getCode(); + IHierarchicalContentProvider getContentProvider = + getContentProvider(); + FtpFile childFtpFile = - FtpFileFactory.createFtpFile(evalElement.dataSet.getCode(), childPath, - evalElement.contentNode, fileFilter); + FtpFileFactory.createFtpFile(dataSetCode, childPath, + evalElement.contentNode, getContentProvider.asContent(dataSetCode), + fileFilter); result.add(childFtpFile); } } @@ -283,27 +467,9 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return result; } - private FtpFileEvaluationContext evaluateExperimentDataSets(String experimentId, - FtpPathResolverContext resolverContext) - { - IETLLIMSService service = resolverContext.getService(); - String sessionToken = resolverContext.getSessionToken(); - - Experiment experiment = tryGetExperiment(experimentId, service, sessionToken); - if (experiment == null) - { - // cannot resolve an existing experiment from the specified path - return null; - } - List<ExternalData> dataSets = - service.listDataSetsByExperimentID(sessionToken, new TechId(experiment)); - - return evaluateDataSetPaths(dataSets); - } - private FtpFileEvaluationContext evaluateDataSetPaths(List<ExternalData> dataSets) { - FtpFileEvaluationContext evalContext = new FtpFileEvaluationContext(); + FtpFileEvaluationContext evalContext = createFtpFileEvaluationContext(); for (int disambiguationIdx = 0; disambiguationIdx < dataSets.size(); disambiguationIdx++) { @@ -397,7 +563,7 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, */ private List<IHierarchicalContentNode> getFileNamesRequiredByTemplate(IHierarchicalContentNode rootNode) { - if (isVariablePresentInTemplate(FILE_NAME_VARNAME)) + if (fileNamePresent) { if (rootNode.isDirectory()) { @@ -409,7 +575,7 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, private String evaluateTemplate(ExternalData dataSet, String fileName, String disambiguation) { - Template eval = new Template(template); + Template eval = template.createFreshCopy(); eval.attemptToBind(DATA_SET_CODE_VARNAME, dataSet.getCode()); eval.attemptToBind(DATA_SET_TYPE_VARNAME, dataSet.getDataSetType().getCode()); String dataSetDate = extractDateValue(dataSet.getRegistrationDate()); @@ -424,22 +590,13 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, } /** - * formats a date as it will appear when a {@link #DATA_SET_DATE_VARNAME} is replaced. + * Formats a date as it will appear after template evaluation. */ - private String extractDateValue(Date dataSetDate) + @Private static String extractDateValue(Date dataSetDate) { return DateFormatUtils.format(dataSetDate, DATA_SET_DATE_FORMAT); } - /** - * @return true if the specified variable is present in the template. - */ - private boolean isVariablePresentInTemplate(String variableName) - { - Template parsedTemplate = new Template(template); - return parsedTemplate.getPlaceholderNames().contains(variableName); - } - private Map<String, DataSetTypeConfig> initializeDataSetTypeConfigs( FtpServerConfig ftpServerConfig) { @@ -511,4 +668,18 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return getDataSetTypeConfig(dataSet).fileListSubPath; } + private FtpFileEvaluationContext createFtpFileEvaluationContext() + { + return new FtpFileEvaluationContext(getContentProvider()); + } + + private IHierarchicalContentProvider getContentProvider() + { + if (contentProvider == null) + { + contentProvider = ServiceProvider.getHierarchicalContentProvider(); + } + return contentProvider; + } + } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java index 49ffe07a842..5956f81078d 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/ServiceProvider.java @@ -28,6 +28,7 @@ import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IDataSourceQueryService; import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.ISearchService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; /** * Provider of remote service onto openBIS. @@ -100,6 +101,11 @@ public class ServiceProvider return ((IEncapsulatedOpenBISService) getApplicationContext().getBean("openBIS-service")); } + public static IGeneralInformationService getGeneralInformationService() + { + return ((IGeneralInformationService) getApplicationContext().getBean("general-information-service")); + } + public static ISearchService getSearchService() { return ((ISearchService) getApplicationContext().getBean("search-service")); diff --git a/datastore_server/source/java/dssApplicationContext.xml b/datastore_server/source/java/dssApplicationContext.xml index 2c07fc869d6..e3486d1d30e 100644 --- a/datastore_server/source/java/dssApplicationContext.xml +++ b/datastore_server/source/java/dssApplicationContext.xml @@ -33,6 +33,12 @@ <constructor-arg value="${server-timeout-in-minutes}"/> </bean> + <bean id="general-information-service" class="ch.systemsx.cisd.openbis.dss.generic.server.EncapsulatedOpenBISService" + factory-method="createGeneralInformationService"> + <constructor-arg value="${server-url}"/> + <constructor-arg value="${server-timeout-in-minutes}"/> + </bean> + <bean id="sessionHolder" class="ch.systemsx.cisd.openbis.dss.generic.server.openbisauth.OpenBISSessionHolder"> <property name="dataStoreCode" value="${data-store-server-code}"/> </bean> @@ -168,6 +174,7 @@ --> <bean id="ftp-server" class="ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpServer" destroy-method="stop"> <constructor-arg ref="etl-lims-service"/> + <constructor-arg ref="general-information-service"/> <constructor-arg ref="adapted-ftp-user-manager"/> <constructor-arg ref="configProperties" /> </bean> diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfigBuilder.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfigBuilder.java index c9157046a84..582856a4a89 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfigBuilder.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpServerConfigBuilder.java @@ -43,13 +43,20 @@ public class FtpServerConfigBuilder props.put(FtpServerConfig.USE_SSL_KEY, String.valueOf(useSSL)); } - public FtpServerConfigBuilder withTemplate(String template) + public FtpServerConfigBuilder showParentsAndChildren() { - props.setProperty(FtpServerConfig.DATASET_DISPLAY_TEMPLATE_KEY, template); + props.setProperty(FtpServerConfig.SHOW_PARENTS_AND_CHILDREN_KEY, Boolean.TRUE.toString()); return this; } + public FtpServerConfigBuilder withTemplate(String template) + { + props.setProperty(FtpServerConfig.DATASET_DISPLAY_TEMPLATE_KEY, template); + return this; + + } + public FtpServerConfigBuilder withFileListFilter(String dataSetType, String filterPattern) { String key = FtpServerConfig.DATASET_FILELIST_FILTER_KEY + dataSetType; diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java index 3304ad35ba2..8afae30cdfb 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/TemplateBasedDataSetResourceResolverTest.java @@ -17,45 +17,92 @@ package ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.EnumSet; import java.util.List; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.io.IOUtils; import org.apache.ftpserver.ftplet.FtpFile; import org.jmock.Expectations; -import org.springframework.beans.factory.BeanFactory; -import org.testng.AssertJUnit; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.io.hierarchical_content.DefaultFileBasedHierarchicalContentFactory; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode; import ch.systemsx.cisd.common.test.TrackingMockery; +import ch.systemsx.cisd.common.utilities.IDelegatedAction; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpConstants; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpServerConfig; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpServerConfigBuilder; import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; -import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProviderTestWrapper; +import ch.systemsx.cisd.openbis.generic.server.api.v1.Translator; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.Connections; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; -import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSetType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataSet; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IDatasetLocation; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataSetBuilder; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier; import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory; /** * @author Kaloyan Enimanev */ -public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit +@Friend(toClasses=TemplateBasedDataSetResourceResolver.class) +public class TemplateBasedDataSetResourceResolverTest extends AbstractFileSystemTestCase { + private static final class SimpleFileContentProvider implements IHierarchicalContentProvider + { + private final File root; + + SimpleFileContentProvider(File root) + { + this.root = root; + } + + public IHierarchicalContent asContent(ExternalData dataSet) + { + return asContent((IDatasetLocation) dataSet.tryGetAsDataSet()); + } + + public IHierarchicalContent asContent(IDatasetLocation datasetLocation) + { + String dataSetCode = datasetLocation.getDataSetCode(); + return asContent(dataSetCode); + } + + public IHierarchicalContent asContent(String dataSetCode) + { + return asContent(new File(root, dataSetCode)); + } + + public IHierarchicalContent asContent(File datasetDirectory) + { + return new DefaultFileBasedHierarchicalContentFactory().asHierarchicalContent( + datasetDirectory, IDelegatedAction.DO_NOTHING); + } + } + + private static final Date REGISTRATION_DATE = new Date(42); + + private static final String RENDERED_REGISTRATION_DATE = TemplateBasedDataSetResourceResolver + .extractDateValue(REGISTRATION_DATE); private static final String SESSION_TOKEN = "token"; @@ -72,6 +119,9 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit private static final String TEMPLATE_WITH_FILENAMES = "DS-${dataSetType}-${fileName}-${disambiguation}"; + private static final String BIG_TEMPLATE = + "DS-${dataSetType}-${dataSetCode}-${dataSetDate}-${disambiguation}"; + private TrackingMockery context; private IETLLIMSService service; @@ -83,6 +133,15 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit private Experiment experiment; + private IGeneralInformationService generalInfoService; + + private SimpleFileContentProvider simpleFileContentProvider; + + private DataSet ds1; + + private DataSet ds2; + + @Override @BeforeMethod public void setUp() { @@ -92,12 +151,15 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit context = new TrackingMockery(); service = context.mock(IETLLIMSService.class); + generalInfoService = context.mock(IGeneralInformationService.class); - final BeanFactory beanFactory = context.mock(BeanFactory.class); - ServiceProviderTestWrapper.setApplicationContext(beanFactory); - hierarchicalContentProvider = ServiceProviderTestWrapper.mock(context, IHierarchicalContentProvider.class); + hierarchicalContentProvider = context.mock(IHierarchicalContentProvider.class); + File root = new File(workingDirectory, "data-sets"); + root.mkdirs(); + simpleFileContentProvider = new SimpleFileContentProvider(root); + - resolverContext = new FtpPathResolverContext(SESSION_TOKEN, service, null); + resolverContext = new FtpPathResolverContext(SESSION_TOKEN, service, generalInfoService, null); context.checking(new Expectations() { { @@ -107,12 +169,32 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit will(returnValue(experiment)); } }); + + ds1 = + new DataSetBuilder().experiment(experiment).code("ds1").type(DS_TYPE1) + .registrationDate(REGISTRATION_DATE).getDataSet(); + File ds1Root = new File(root, ds1.getCode()); + File ds1Original = new File(ds1Root, "original"); + ds1Original.mkdirs(); + FileUtilities.writeToFile(new File(ds1Original, "hello.txt"), "hello world"); + FileUtilities.writeToFile(new File(ds1Original, "abc.txt"), "abcdefghijklmnopqrstuvwxyz"); + FileUtilities.writeToFile(new File(ds1Original, "some.properties"), "a = alpha\nb = bets"); + ds2 = + new DataSetBuilder().experiment(experiment).code("ds2").type(DS_TYPE2) + .registrationDate(REGISTRATION_DATE).getDataSet(); + File ds2Root = new File(root, ds2.getCode()); + File ds2Original = new File(ds2Root, "original2"); + ds2Original.mkdirs(); + FileUtilities.writeToFile(new File(ds2Original, "hello.txt"), "hello world"); + File dataFolder = new File(ds2Original, "data"); + dataFolder.mkdirs(); + FileUtilities.writeToFile(new File(dataFolder, "a1.tsv"), "t\tlevel\n1.34\t2\n"); + FileUtilities.writeToFile(new File(dataFolder, "a2.tsv"), "t\tlevel\n2.53\t3\n"); } @AfterMethod(alwaysRun = true) public void tearDown(Method m) { - ServiceProviderTestWrapper.restoreApplicationContext(); try { context.assertIsSatisfied(); @@ -121,97 +203,121 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit throw new Error(m.getName() + "() : ", t); } } - + @Test - public void testResolveSimpleTemplate() + public void testInvalidConfig() { FtpServerConfig config = - new FtpServerConfigBuilder().withTemplate(SIMPLE_TEMPLATE).getConfig(); + new FtpServerConfigBuilder().withTemplate(TEMPLATE_WITH_FILENAMES).showParentsAndChildren().getConfig(); + try + { + new TemplateBasedDataSetResourceResolver(config); + fail("ConfigurationFailureException expected"); + } catch (ConfigurationFailureException ex) + { + assertEquals("Template contains file name variable and the flag " + + "to show parents/children data sets is set.", ex.getMessage()); + } + } + + @Test + public void testWithParentsTopLevel() + { + FtpServerConfig config = + new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren().getConfig(); resolver = new TemplateBasedDataSetResourceResolver(config); - - final String dataSetCode = "dataSetCode"; - - String path = EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetCode; - - List<ExternalData> dataSets = - Arrays.asList(createDataSet("randomCode", "randomType"), - createDataSet(dataSetCode, DS_TYPE1), - createDataSet("randomCode2", "randomType2")); - + resolver.setContentProvider(simpleFileContentProvider); + + ds1.setParents(Arrays.<ExternalData>asList(ds2)); + final List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1); + prepareExperimentListExpectations(dataSets); - - context.checking(new Expectations() - { - { - IHierarchicalContent content = getHierarchicalContentMock(dataSetCode); - IHierarchicalContentNode rootNode = getHierarchicalRootNodeMock(dataSetCode); - - one(content).getNode(StringUtils.EMPTY); - will(returnValue(rootNode)); - - exactly(2).of(rootNode).isDirectory(); - will(returnValue(true)); - - one(rootNode).getFile(); - will(throwException(new UnsupportedOperationException())); - - } - }); - + prepareGetDataSetMetaData(ds1); + prepareListDataSetsByCode(ds2); + + String dataSetPathElement = "DS-DS_TYPE1-ds1-" + RENDERED_REGISTRATION_DATE + "-A"; + String path = EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetPathElement; FtpFile ftpFile = resolver.resolve(path, resolverContext); - - assertNotNull(ftpFile); - assertEquals(dataSetCode, ftpFile.getName()); - + + assertEquals(dataSetPathElement, ftpFile.getName()); + assertEquals(path, ftpFile.getAbsolutePath()); + assertEquals(true, ftpFile.isDirectory()); + List<FtpFile> files = ftpFile.listFiles(); + assertEquals("PARENT-DS-DS_TYPE2-ds2-" + RENDERED_REGISTRATION_DATE + "-A", files.get(0).getName()); + assertEquals(true, files.get(0).isDirectory()); + assertEquals("original", files.get(1).getName()); + assertEquals(true, files.get(1).isDirectory()); + assertEquals(2, files.size()); } - + @Test - public void testResolveNestedFilesWithSimpleTemplate() + public void testChildOfParent() { FtpServerConfig config = - new FtpServerConfigBuilder().withTemplate(SIMPLE_TEMPLATE).getConfig(); + new FtpServerConfigBuilder().withTemplate(BIG_TEMPLATE).showParentsAndChildren().getConfig(); resolver = new TemplateBasedDataSetResourceResolver(config); - - final String dataSetCode = "dataSetCode"; - final String subPath = "level1/level2/fileName.txt"; - + resolver.setContentProvider(simpleFileContentProvider); + + ds1.setParents(Arrays.<ExternalData>asList(ds2)); + ds2.setChildren(Arrays.<ExternalData>asList(ds1)); + final List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1); + + prepareExperimentListExpectations(dataSets); + prepareGetDataSetMetaData(ds1); + prepareListDataSetsByCode(ds2); + prepareGetDataSetMetaData(ds2); + prepareListDataSetsByCode(ds1); + + String dataSetPathElement = "DS-DS_TYPE1-ds1-" + RENDERED_REGISTRATION_DATE + "-A"; + String ds2AsParent = "PARENT-DS-DS_TYPE2-ds2-" + RENDERED_REGISTRATION_DATE + "-A"; String path = - EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetCode + FtpConstants.FILE_SEPARATOR - + subPath; + EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetPathElement + + FtpConstants.FILE_SEPARATOR + ds2AsParent; + FtpFile ftpFile = resolver.resolve(path, resolverContext); + + assertEquals(ds2AsParent, ftpFile.getName()); + assertEquals(path, ftpFile.getAbsolutePath()); + assertEquals(true, ftpFile.isDirectory()); + List<FtpFile> files = ftpFile.listFiles(); + assertEquals("CHILD-DS-DS_TYPE1-ds1-" + RENDERED_REGISTRATION_DATE + "-A", files.get(0).getName()); + assertEquals(true, files.get(0).isDirectory()); + assertEquals("original2", files.get(1).getName()); + assertEquals(true, files.get(1).isDirectory()); + assertEquals(2, files.size()); + } + + @Test + public void testResolveNestedFilesWithSimpleTemplate() throws IOException + { + FtpServerConfig config = + new FtpServerConfigBuilder().withTemplate(SIMPLE_TEMPLATE).showParentsAndChildren() + .getConfig(); + resolver = new TemplateBasedDataSetResourceResolver(config); + resolver.setContentProvider(simpleFileContentProvider); - List<ExternalData> dataSets = Arrays.asList(createDataSet(dataSetCode, DS_TYPE1)); + ds1.setParents(Arrays.<ExternalData> asList(ds2)); + ds2.setChildren(Arrays.<ExternalData> asList(ds1)); + final List<ExternalData> dataSets = Arrays.<ExternalData> asList(ds1); prepareExperimentListExpectations(dataSets); + prepareGetDataSetMetaData(ds1); + prepareListDataSetsByCode(ds2); + prepareGetDataSetMetaData(ds2); + prepareListDataSetsByCode(ds1); - context.checking(new Expectations() - { - { - IHierarchicalContent content = getHierarchicalContentMock(dataSetCode); - IHierarchicalContentNode mockNode = - context.mock(IHierarchicalContentNode.class); - - allowing(content).getNode(subPath); - will(returnValue(mockNode)); - - one(mockNode).getRelativePath(); - will(returnValue(subPath)); - - exactly(2).of(mockNode).isDirectory(); - will(returnValue(false)); - - one(mockNode).getFileLength(); - will(returnValue(2L)); - - one(mockNode).getFile(); - will(returnValue(null)); - } - }); - + String path = + EXP_ID + FtpConstants.FILE_SEPARATOR + "ds1" + FtpConstants.FILE_SEPARATOR + + "PARENT-ds2" + FtpConstants.FILE_SEPARATOR + "original2" + + FtpConstants.FILE_SEPARATOR + "data" + FtpConstants.FILE_SEPARATOR + + "a1.tsv"; FtpFile ftpFile = resolver.resolve(path, resolverContext); - assertNotNull(ftpFile); - assertEquals("fileName.txt", ftpFile.getName()); - assertTrue(ftpFile.isFile()); + assertEquals("a1.tsv", ftpFile.getName()); + assertEquals(path, ftpFile.getAbsolutePath()); + assertEquals(true, ftpFile.isFile()); + InputStream fileContent = ftpFile.createInputStream(0); + assertEquals("[t\tlevel, 1.34\t2]", IOUtils.readLines(fileContent).toString()); + fileContent.close(); } @Test @@ -220,57 +326,67 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit FtpServerConfig config = new FtpServerConfigBuilder().withTemplate(SIMPLE_TEMPLATE).getConfig(); resolver = new TemplateBasedDataSetResourceResolver(config); + resolver.setContentProvider(hierarchicalContentProvider); - final String dataSetCode = "dataSetCode"; final String subPath = "fileName.txt"; String path = - EXP_ID + FtpConstants.FILE_SEPARATOR + dataSetCode + FtpConstants.FILE_SEPARATOR + EXP_ID + FtpConstants.FILE_SEPARATOR + ds1.getCode() + FtpConstants.FILE_SEPARATOR + subPath; - List<ExternalData> dataSets = Arrays.asList(createDataSet(dataSetCode, DS_TYPE1)); + List<ExternalData> dataSets = Arrays.<ExternalData>asList(ds1); prepareExperimentListExpectations(dataSets); context.checking(new Expectations() { { - IHierarchicalContent content = getHierarchicalContentMock(dataSetCode); + IHierarchicalContent content = context.mock(IHierarchicalContent.class, ds1.getCode()); - one(hierarchicalContentProvider).asContent(dataSetCode); + one(hierarchicalContentProvider).asContent((ExternalData) ds1); will(returnValue(content)); - one(content).close(); - - IHierarchicalContentNode mockNode = - context.mock(IHierarchicalContentNode.class); - - ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {}); - - allowing(content).getNode(subPath); - will(returnValue(mockNode)); - - one(mockNode).getRelativePath(); + + IHierarchicalContentNode rootNode = + context.mock(IHierarchicalContentNode.class, "root"); + IHierarchicalContentNode fileNode = + context.mock(IHierarchicalContentNode.class, "file"); + + one(content).getRootNode(); + will(returnValue(rootNode)); + + one(rootNode).getChildNodes(); + will(returnValue(Arrays.asList(fileNode))); + + one(fileNode).getName(); will(returnValue(subPath)); - - exactly(2).of(mockNode).isDirectory(); + + one(fileNode).getRelativePath(); + will(returnValue(subPath)); + + exactly(2).of(fileNode).isDirectory(); will(returnValue(false)); - - one(mockNode).getFileLength(); + + one(fileNode).getFileLength(); will(returnValue(2L)); - - one(mockNode).getFile(); + + one(fileNode).getFile(); will(returnValue(null)); - - one(mockNode).getInputStream(); + + one(fileNode).getInputStream(); + ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {}); will(returnValue(is)); - + + allowing(content).getNode(subPath); + will(returnValue(fileNode)); + + atLeast(1).of(content).close(); } }); FtpFile ftpFile = resolver.resolve(path, resolverContext); assertNotNull(ftpFile); - assertEquals("fileName.txt", ftpFile.getName()); + assertEquals(subPath, ftpFile.getName()); assertTrue(ftpFile.isFile()); InputStream fileContent = ftpFile.createInputStream(0); // this call will also close the IHierarchicalContent @@ -278,59 +394,33 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit } @Test - public void testFileFilters() + public void testSubPathAndFileFilters() { - String filterPattern = "[^.]*\\.txt"; FtpServerConfig config = new FtpServerConfigBuilder().withTemplate(TEMPLATE_WITH_FILENAMES) - .withFileListFilter(DS_TYPE1, filterPattern).getConfig(); + .withFileListSubPath(DS_TYPE1, "orig[^/]*") + .withFileListFilter(DS_TYPE1, "[^.]*\\.txt").getConfig(); resolver = new TemplateBasedDataSetResourceResolver(config); - - final String dataSetCode = "dataSetCode"; - final String dataSetCode2 = "dataSetCode2"; + resolver.setContentProvider(simpleFileContentProvider); List<ExternalData> dataSets = - Arrays.asList(createDataSet(dataSetCode, DS_TYPE1), - createDataSet(dataSetCode2, DS_TYPE2)); + Arrays.<ExternalData>asList(ds1, ds2); prepareExperimentListExpectations(dataSets); - context.checking(new Expectations() - { - { - IHierarchicalContentNode rootNode = getHierarchicalRootNodeMock(dataSetCode); - allowing(rootNode).isDirectory(); - will(returnValue(true)); - - allowing(rootNode).getChildNodes(); - List<IHierarchicalContentNode> children = - createFileNodeMocks("file.exe", "file.txt", "file"); - will(returnValue(children)); - - IHierarchicalContentNode rootNode2 = getHierarchicalRootNodeMock(dataSetCode2); - allowing(rootNode2).isDirectory(); - will(returnValue(true)); - - allowing(rootNode2).getChildNodes(); - List<IHierarchicalContentNode> children2 = - createFileNodeMocks("file.jar", "file.sh"); - will(returnValue(children2)); - } - }); - List<FtpFile> files = resolver.listExperimentChildrenPaths(experiment, EXP_ID, resolverContext); assertNotNull(files); - assertEquals(3, files.size()); - + assertEquals("DS-DS_TYPE1-abc.txt-A", files.get(0).getName()); assertTrue(files.get(0).isFile()); - assertEquals("DS-DS_TYPE1-file.txt-A", files.get(0).getName()); + assertEquals(26, files.get(0).getSize()); + assertEquals("DS-DS_TYPE1-hello.txt-A", files.get(1).getName()); assertTrue(files.get(1).isFile()); - assertEquals("DS-DS_TYPE2-file.jar-B", files.get(1).getName()); - assertTrue(files.get(2).isFile()); - assertEquals("DS-DS_TYPE2-file.sh-B", files.get(2).getName()); - + assertEquals(11, files.get(1).getSize()); + assertEquals("DS-DS_TYPE2-original2-B", files.get(2).getName()); + assertTrue(files.get(2).isDirectory()); + assertEquals(3, files.size()); } @@ -343,45 +433,72 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit new TechId(experiment)); will(returnValue(dataSets)); - for (ExternalData dataSet : dataSets) - { - String mockName = getHierarchicalContentMockName(dataSet.getCode()); - IHierarchicalContent content = - context.mock(IHierarchicalContent.class, mockName); - one(hierarchicalContentProvider).asContent(dataSet); - will(returnValue(content)); - one(content).close(); - - String rootMockName = getRootNodeMockName(dataSet.getCode()); - IHierarchicalContentNode rootNode = - context.mock(IHierarchicalContentNode.class, rootMockName); - allowing(content).getRootNode(); - will(returnValue(rootNode)); - - allowing(rootNode).getRelativePath(); - will(returnValue(StringUtils.EMPTY)); - - allowing(rootNode).getName(); - will(returnValue(StringUtils.EMPTY)); - } +// for (ExternalData dataSet : dataSets) +// { +// String mockName = getHierarchicalContentMockName(dataSet.getCode()); +// IHierarchicalContent content = +// context.mock(IHierarchicalContent.class, mockName); +// one(hierarchicalContentProvider).asContent(dataSet); +// will(returnValue(content)); +// one(content).close(); +// +// String rootMockName = getRootNodeMockName(dataSet.getCode()); +// IHierarchicalContentNode rootNode = +// context.mock(IHierarchicalContentNode.class, rootMockName); +// allowing(content).getRootNode(); +// will(returnValue(rootNode)); +// +// allowing(rootNode).getRelativePath(); +// will(returnValue(StringUtils.EMPTY)); +// +// allowing(rootNode).getName(); +// will(returnValue(StringUtils.EMPTY)); +// } } }); } - private String getHierarchicalContentMockName(String dataSetCode) + private void prepareGetDataSetMetaData(final ExternalData... dataSets) { - return dataSetCode; + final List<String> codes = extractCodes(dataSets); + final List<ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet> translateDataSets = + Translator.translate(Arrays.asList(dataSets), + EnumSet.of(Connections.PARENTS, Connections.CHILDREN)); + context.checking(new Expectations() + { + { + one(generalInfoService).getDataSetMetaData(SESSION_TOKEN, codes); + will(returnValue(translateDataSets)); + } + }); } - private String getRootNodeMockName(String dataSetCode) + private void prepareListDataSetsByCode(final ExternalData... dataSets) + { + final List<String> codes = extractCodes(dataSets); + context.checking(new Expectations() + { + { + one(service).listDataSetsByCode(SESSION_TOKEN, codes); + will(returnValue(Arrays.asList(dataSets))); + } + }); + + } + + private List<String> extractCodes(final ExternalData... dataSets) { - return dataSetCode + "-rootNode"; + final List<String> codes = new ArrayList<String>(); + for (ExternalData dataSet : dataSets) + { + codes.add(dataSet.getCode()); + } + return codes; } - - private IHierarchicalContentNode getHierarchicalRootNodeMock(String dataSetCode) + + private String getHierarchicalContentMockName(String dataSetCode) { - String mockName = getRootNodeMockName(dataSetCode); - return context.getMock(mockName, IHierarchicalContentNode.class); + return dataSetCode; } protected IHierarchicalContent getHierarchicalContentMock(String dataSetCode) @@ -390,51 +507,4 @@ public class TemplateBasedDataSetResourceResolverTest extends AssertJUnit return context.getMock(mockName, IHierarchicalContent.class); } - private ExternalData createDataSet(String dataSetCode, String dataSetType) - { - return createDataSet(dataSetCode, dataSetType, new Date()); - } - - private ExternalData createDataSet(String dataSetCode, String dataSetType, Date registrationDate) - { - ExternalData result = new ExternalData(); - result.setCode(dataSetCode); - DataSetType type = new DataSetType(dataSetType); - result.setDataSetType(type); - result.setRegistrationDate(registrationDate); - return result; - } - - private List<IHierarchicalContentNode> createFileNodeMocks(final String... fileNames) - { - final List<IHierarchicalContentNode> result = new ArrayList<IHierarchicalContentNode>(); - - context.checking(new Expectations() - { - { - for (String fileName : fileNames) - { - IHierarchicalContentNode mockNode = - context.mock(IHierarchicalContentNode.class, fileName); - result.add(mockNode); - - allowing(mockNode).getFileLength(); - will(returnValue(10L)); - - allowing(mockNode).getFile(); - will(throwException(new UnsupportedOperationException())); - - allowing(mockNode).getName(); - will(returnValue(fileName)); - - allowing(mockNode).isDirectory(); - will(returnValue(false)); - - allowing(mockNode).getRelativePath(); - will(returnValue(fileName)); - } - } - }); - return result; - } } -- GitLab