From 45b3d627c75aa897e58221c0c71cc41479b055fc Mon Sep 17 00:00:00 2001 From: kaloyane <kaloyane> Date: Mon, 30 May 2011 10:02:34 +0000 Subject: [PATCH] LMS-2257: FTP server does not close IHierarchicalContent handles SVN: 21504 --- .../generic/server/ftp/FtpFileFactory.java | 78 +++++ ...HierarchicalContentClosingInputStream.java | 52 ++++ .../resolver/FtpFileEvaluationContext.java | 106 +++++++ ...ToFtpFileAdapter.java => FtpFileImpl.java} | 81 ++++-- .../resolver/IExperimentChildrenLister.java | 7 + .../TemplateBasedDataSetResourceResolver.java | 271 ++++++++++-------- 6 files changed, 453 insertions(+), 142 deletions(-) create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/HierarchicalContentClosingInputStream.java create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java rename datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/{HierarchicalContentToFtpFileAdapter.java => FtpFileImpl.java} (54%) 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 new file mode 100644 index 00000000000..f80cc1c6ec9 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpFileFactory.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011 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.ftp; + +import java.io.File; + +import org.apache.ftpserver.ftplet.FtpFile; + +import ch.systemsx.cisd.common.io.IHierarchicalContentNode; +import ch.systemsx.cisd.common.io.IHierarchicalContentNodeFilter; +import ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver.FtpFileImpl; + +/** + * A factory constructing {@link FtpFile} objects. + * + * @author Kaloyan Enimanev + */ +public class FtpFileFactory +{ + + /** + * This is just a convenience method that retrieves all necessary data from an + * {@link IHierarchicalContentNode} and constructs an {@link FtpFile}. + * <p> + * The reference to {@link IHierarchicalContentNode} is *not* kept by the returned instance. + * Thus, callers are responsible for closing all resources associated with the + * {@link IHierarchicalContentNode} on their own. + */ + public static FtpFile createFtpFile(String dataSetCode, String path, + IHierarchicalContentNode contentNode, IHierarchicalContentNodeFilter childrenFilter) + { + return new FtpFileImpl(dataSetCode, path, contentNode.getRelativePath(), + contentNode.isDirectory(), getSize(contentNode), + getLastModified(contentNode), childrenFilter); + + } + + private static long getSize(IHierarchicalContentNode contentNode) + { + if (contentNode.isDirectory()) + { + return 0; + } else + { + return contentNode.getFileLength(); + } + } + + private static long getLastModified(IHierarchicalContentNode contentNode) + { + try + { + File file = contentNode.getFile(); + if (file != null) + { + return file.lastModified(); + } + } catch (UnsupportedOperationException uoe) + { + // ignore + } + return 0; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/HierarchicalContentClosingInputStream.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/HierarchicalContentClosingInputStream.java new file mode 100644 index 00000000000..d4e1c4d26a3 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/HierarchicalContentClosingInputStream.java @@ -0,0 +1,52 @@ +/* + * Copyright 2011 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.ftp; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import ch.systemsx.cisd.common.io.IHierarchicalContent; + +/** + * An {@link InputStream} implementation which closes an associated {@link IHierarchicalContent} + * together with an underlying target {@link InputStream}. + * + * @author Kaloyan Enimanev + */ +public class HierarchicalContentClosingInputStream extends FilterInputStream +{ + private final IHierarchicalContent hierarchicalContent; + + public HierarchicalContentClosingInputStream(InputStream target, + IHierarchicalContent hierarchicalContent) + { + super(target); + this.hierarchicalContent = hierarchicalContent; + } + + @Override + public void close() throws IOException + { + // no error can be throw here + hierarchicalContent.close(); + + // can throw IOException + super.close(); + } + +} 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 new file mode 100644 index 00000000000..02c0762b229 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileEvaluationContext.java @@ -0,0 +1,106 @@ +/* + * Copyright 2011 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.ftp.resolver; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; + +import ch.systemsx.cisd.common.io.IHierarchicalContent; +import ch.systemsx.cisd.common.io.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; + +/** + * An object holding templates evaluation result data. + * + * @author Kaloyan Enimanev + */ +public class FtpFileEvaluationContext +{ + + static class EvaluatedElement + { + ExternalData dataSet; + + // will only be filled when the ${fileName} variable + // is used in the template + String pathInDataSet = StringUtils.EMPTY; + + String evaluatedTemplate; + + IHierarchicalContentNode contentNode; + } + + private Map<String /* dataset code */, IHierarchicalContent> contents = + new HashMap<String, IHierarchicalContent>(); + + private List<EvaluatedElement> evaluatedPaths = new ArrayList<EvaluatedElement>(); + + /** + * @return the evaluation result. + */ + public List<EvaluatedElement> getEvalElements() + { + return Collections.unmodifiableList(evaluatedPaths); + + } + + /** + * Adds a collection of {@link EvaluatedElement} to the results. + */ + public void addEvaluatedElements(Collection<EvaluatedElement> evaluatedPath) + { + evaluatedPaths.addAll(evaluatedPath); + } + + public IHierarchicalContent getHierarchicalContent(String dataSetCode) + { + IHierarchicalContent result = contents.get(dataSetCode); + if (result == null) + { + result = createHierarchicalContent(dataSetCode); + contents.put(dataSetCode, result); + } + return result; + } + + /** + * closes the evaluation context and frees all associated resources. + */ + public void close() + { + for (IHierarchicalContent content : contents.values()) + { + content.close(); + } + contents.clear(); + } + + private IHierarchicalContent createHierarchicalContent(String code) + { + IHierarchicalContentProvider provider = ServiceProvider.getHierarchicalContentProvider(); + return provider.asContent(code); + } + +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/HierarchicalContentToFtpFileAdapter.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java similarity index 54% rename from datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/HierarchicalContentToFtpFileAdapter.java rename to datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java index d8587f6ecb4..38b6d4c3728 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/HierarchicalContentToFtpFileAdapter.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/FtpFileImpl.java @@ -23,34 +23,58 @@ import java.util.List; import org.apache.ftpserver.ftplet.FtpFile; +import ch.systemsx.cisd.common.io.IHierarchicalContent; import ch.systemsx.cisd.common.io.IHierarchicalContentNode; import ch.systemsx.cisd.common.io.IHierarchicalContentNodeFilter; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpConstants; +import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpFileFactory; +import ch.systemsx.cisd.openbis.dss.generic.server.ftp.HierarchicalContentClosingInputStream; +import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; /** - * An {@link FtpFile} implementation adapting an underlying {@link IHierarchicalContentNode}. + * An {@link FtpFile} implementation which lazily creates and uses {@link IHierarchicalContent} when + * needed. * <p> - * The resources represented by {@link HierarchicalContentToFtpFileAdapter} exist in the data store. + * The resources represented by {@link FtpFileImpl} exist in the data store. * * @author Kaloyan Enimanev */ -public class HierarchicalContentToFtpFileAdapter extends AbstractFtpFile +public class FtpFileImpl extends AbstractFtpFile { - private final IHierarchicalContentNode contentNode; + private final String dataSetCode; + + private final String pathInDataSet; + + private final boolean isDirectory; + + private final long size; + + private final long lastModified; private final IHierarchicalContentNodeFilter childrenFilter; - public HierarchicalContentToFtpFileAdapter(String path, IHierarchicalContentNode contentNode, + public FtpFileImpl(String dataSetCode, String path, String pathInDataSet, boolean isDirectory, + long size, + long lastModified, IHierarchicalContentNodeFilter childrenFilter) { super(path); - this.contentNode = contentNode; + this.dataSetCode = dataSetCode; + this.pathInDataSet = pathInDataSet; + this.isDirectory = isDirectory; + this.size = size; + this.lastModified = lastModified; this.childrenFilter = childrenFilter; } public InputStream createInputStream(long offset) throws IOException { - InputStream result = contentNode.getInputStream(); + IHierarchicalContent content = createHierarchicalContent(); + IHierarchicalContentNode contentNode = getContentNodeForThisFile(content); + + InputStream result = + new HierarchicalContentClosingInputStream(contentNode.getInputStream(), content); + if (offset > 0) { result.skip(offset); @@ -60,13 +84,7 @@ public class HierarchicalContentToFtpFileAdapter extends AbstractFtpFile public long getLastModified() { - try - { - return contentNode.getFile().lastModified(); - } catch (UnsupportedOperationException uoe) - { - return 0; - } + return lastModified; } @@ -74,14 +92,14 @@ public class HierarchicalContentToFtpFileAdapter extends AbstractFtpFile { if (isFile()) { - return contentNode.getFileLength(); + return size; } return 0; } public boolean isDirectory() { - return contentNode.isDirectory(); + return isDirectory; } public boolean isFile() @@ -92,30 +110,45 @@ public class HierarchicalContentToFtpFileAdapter extends AbstractFtpFile @Override public List<org.apache.ftpserver.ftplet.FtpFile> unsafeListFiles() { - if (isDirectory()) + if (isFile()) { + throw new UnsupportedOperationException(); + } + + IHierarchicalContent content = createHierarchicalContent(); + try + { + IHierarchicalContentNode contentNode = getContentNodeForThisFile(content); List<IHierarchicalContentNode> children = contentNode.getChildNodes(); List<org.apache.ftpserver.ftplet.FtpFile> result = new ArrayList<org.apache.ftpserver.ftplet.FtpFile>(); for (IHierarchicalContentNode childNode : children) - { + { if (childrenFilter.accept(childNode)) { String childPath = absolutePath + FtpConstants.FILE_SEPARATOR + childNode.getName(); - HierarchicalContentToFtpFileAdapter childFile = - new HierarchicalContentToFtpFileAdapter(childPath, childNode, + FtpFile childFile = + FtpFileFactory.createFtpFile(dataSetCode, childPath, childNode, childrenFilter); result.add(childFile); } - } + } return result; - - } else + } finally { - throw new UnsupportedOperationException(); + content.close(); } } + private IHierarchicalContent createHierarchicalContent() + { + return ServiceProvider.getHierarchicalContentProvider().asContent(dataSetCode); + } + + private IHierarchicalContentNode getContentNodeForThisFile(IHierarchicalContent content) + { + return content.getNode(pathInDataSet); + } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/IExperimentChildrenLister.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/IExperimentChildrenLister.java index 9bb58c54550..6a4afb02d71 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/IExperimentChildrenLister.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/resolver/IExperimentChildrenLister.java @@ -24,10 +24,17 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; /** + * An interface decoupling the resolver implementations for experiments and data sets. + * * @author Kaloyan Enimanev */ public interface IExperimentChildrenLister { + /** + * Lists the children {@link FtpFile} objects in an experiment. + * + * @param parentPath the FTP path representing the experiment. + */ List<FtpFile> listExperimentChildrenPaths(Experiment experiment, String parentPath, FtpPathResolverContext context); 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 fd0f26da13d..d70d236bab4 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 @@ -37,11 +37,11 @@ import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; import ch.systemsx.cisd.common.utilities.Template; import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpConstants; +import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpFileFactory; 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.shared.IHierarchicalContentProvider; -import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; +import ch.systemsx.cisd.openbis.dss.generic.server.ftp.resolver.FtpFileEvaluationContext.EvaluatedElement; import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService; import ch.systemsx.cisd.openbis.generic.shared.basic.TechId; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Experiment; @@ -51,9 +51,10 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifi /** * Resolves paths like - * /<space-code>/<project-code>/<experiment-code>/<dataset-template>[/<sub-path>]* + * "/<space-code>/<project-code>/<experiment-code>/<dataset-template>[/<sub-path>]*" to + * {@link FtpFile} objects. * <p> - * Subpaths are resolved as a relative paths starting from the root of a dataset. + * Subpaths are resolved as relative paths starting from the root of a dataset. * <p> * * @author Kaloyan Enimanev @@ -92,19 +93,6 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, IHierarchicalContentNodeFilter fileFilter = IHierarchicalContentNodeFilter.MATCH_ALL; } - private static class EvaluatedDataSetPath - { - ExternalData dataSet; - - // will only be filled when the ${fileName} variable - // is used in the template - String fileName = StringUtils.EMPTY; - - String evaluatedTemplate; - - IHierarchicalContentNode contentNode; - } - /** * a template, that can contain special variables. * @@ -125,35 +113,6 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, } - private Map<String, DataSetTypeConfig> initializeDataSetTypeConfigs( - FtpServerConfig ftpServerConfig) - { - Map<String, DataSetTypeConfig> result = new HashMap<String, DataSetTypeConfig>(); - Map<String, String> fileListSubPaths = ftpServerConfig.getFileListSubPaths(); - Map<String, String> fileListFilters = ftpServerConfig.getFileListFilters(); - - for (Entry<String, String> subPathEntry : fileListSubPaths.entrySet()) - { - DataSetTypeConfig dsConfig = new DataSetTypeConfig(); - dsConfig.fileListSubPath = subPathEntry.getValue(); - result.put(subPathEntry.getKey(), dsConfig); - } - - for (Entry<String, String> filterEntry : fileListFilters.entrySet()) - { - String dataSetType = filterEntry.getKey(); - String fileFilterPattern = filterEntry.getValue(); - DataSetTypeConfig dsConfig = result.get(dataSetType); - if (dsConfig == null) - { - dsConfig = new DataSetTypeConfig(); - } - dsConfig.fileFilter = createFilter(fileFilterPattern); - result.put(dataSetType, dsConfig); - } - return result; - } - /** * @return <code>true</code> for paths containing at least 4 nested directory levels. */ @@ -165,75 +124,68 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, public FtpFile resolve(String path, final FtpPathResolverContext resolverContext) { - IETLLIMSService service = resolverContext.getService(); - String sessionToken = resolverContext.getSessionToken(); - - EvaluatedDataSetPath evaluationResult = - tryExtractDataSetAndFileName(path, service, sessionToken); - if (evaluationResult == null) + String experimentId = extractExperimentIdFromPath(path); + FtpFileEvaluationContext evalContext = + evaluateExperimentDataSets(experimentId, resolverContext); + if (evalContext == null) { return null; } - String nestedSubPath = extractNestedSubPath(path); - String relativePath = evaluationResult.fileName; - if (false == StringUtils.isBlank(nestedSubPath)) + try { - if (StringUtils.isBlank(relativePath)) - { - relativePath = nestedSubPath; - } else - { - relativePath += FtpConstants.FILE_SEPARATOR + nestedSubPath; - } + return extractMatchingFileOrNull(path, experimentId, evalContext); + } finally + { + evalContext.close(); } + } + + private FtpFile extractMatchingFileOrNull(String path, String experimentId, + FtpFileEvaluationContext evalContext) + { + EvaluatedElement matchingElement = + tryFindMatchingEvalElement(path, experimentId, evalContext); - IHierarchicalContentNodeFilter fileFilter = getFileFilter(evaluationResult.dataSet); - IHierarchicalContentProvider provider = ServiceProvider.getHierarchicalContentProvider(); - IHierarchicalContent content = provider.asContent(evaluationResult.dataSet.getCode()); - // FIXME use content.close() - otherwise data set will be locked until timeout - IHierarchicalContentNode contentNode = content.getNode(relativePath); + String pathInDataSet = extractPathInDataSet(path); + String hierarchicalNodePath = + constructHierarchicalNodePath(matchingElement.pathInDataSet, pathInDataSet); + + ExternalData dataSet = matchingElement.dataSet; + IHierarchicalContentNodeFilter fileFilter = getFileFilter(dataSet); + IHierarchicalContent content = evalContext.getHierarchicalContent(dataSet.getCode()); + IHierarchicalContentNode contentNode = content.getNode(hierarchicalNodePath); if (fileFilter.accept(contentNode)) { - return new HierarchicalContentToFtpFileAdapter(path, contentNode, fileFilter); + return FtpFileFactory.createFtpFile(dataSet.getCode(), path, contentNode, fileFilter); } else { return null; } } - private EvaluatedDataSetPath tryExtractDataSetAndFileName(String path, IETLLIMSService service, - String sessionToken) + private EvaluatedElement tryFindMatchingEvalElement(String path, String experimentId, + FtpFileEvaluationContext evalContext) { - String experimentId = extractExperimentIdentifier(path); - Experiment experiment = tryExtractExperiment(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)); String pathWithEndSlash = path + FtpConstants.FILE_SEPARATOR; - - for (EvaluatedDataSetPath evaluatedPath : evaluateDataSetPaths(dataSets)) + for (EvaluatedElement evalElement : evalContext.getEvalElements()) { String fullEvaluatedPath = - experimentId + FtpConstants.FILE_SEPARATOR + evaluatedPath.evaluatedTemplate + experimentId + FtpConstants.FILE_SEPARATOR + evalElement.evaluatedTemplate + FtpConstants.FILE_SEPARATOR; if (pathWithEndSlash.startsWith(fullEvaluatedPath)) { - return evaluatedPath; + return evalElement; } } - return null; } /** + * @param path the path we are trying to resolve * @return the nested path within the data set (i.e. under the dataset directory level) */ - private String extractNestedSubPath(String path) + private String extractPathInDataSet(String path) { String[] levels = StringUtils.split(path, FtpConstants.FILE_SEPARATOR); if (levels.length > 4) @@ -245,7 +197,23 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, } } - private Experiment tryExtractExperiment(String experimentId, IETLLIMSService service, + private String constructHierarchicalNodePath(String relativePath, String pathInDataSet) + { + String result = relativePath; + if (false == StringUtils.isBlank(pathInDataSet)) + { + if (StringUtils.isBlank(relativePath)) + { + result = pathInDataSet; + } else + { + result += FtpConstants.FILE_SEPARATOR + pathInDataSet; + } + } + return result; + } + + private Experiment tryGetExperiment(String experimentId, IETLLIMSService service, String sessionToken) { ExperimentIdentifier experimentIdentifier = @@ -263,7 +231,7 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return result; } - private String extractExperimentIdentifier(String path) + private String extractExperimentIdFromPath(String path) { String[] levels = StringUtils.split(path, FtpConstants.FILE_SEPARATOR); String experimentId = @@ -272,43 +240,84 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return experimentId; } - public List<FtpFile> listExperimentChildrenPaths(Experiment experiment, String parentPath, - FtpPathResolverContext context) + /** + * @see IExperimentChildrenLister + */ + public List<FtpFile> listExperimentChildrenPaths(Experiment experiment, + final String parentPath, FtpPathResolverContext context) { IETLLIMSService service = context.getService(); String sessionToken = context.getSessionToken(); - List<FtpFile> result = new ArrayList<FtpFile>(); List<ExternalData> dataSets = service.listDataSetsByExperimentID(sessionToken, new TechId(experiment)); - for (EvaluatedDataSetPath evaluationResult : evaluateDataSetPaths(dataSets)) + + FtpFileEvaluationContext evalContext = evaluateDataSetPaths(dataSets); + try + { + return createFtpFilesFromEvaluationResult(parentPath, evalContext); + } finally + { + evalContext.close(); + } + } + + private List<FtpFile> createFtpFilesFromEvaluationResult(final String parentPath, + FtpFileEvaluationContext evalResult) + { + ArrayList<FtpFile> result = new ArrayList<FtpFile>(); + for (EvaluatedElement evalElement : evalResult.getEvalElements()) { - IHierarchicalContentNodeFilter fileFilter = getFileFilter(evaluationResult.dataSet); - if (fileFilter.accept(evaluationResult.contentNode)) + IHierarchicalContentNodeFilter fileFilter = getFileFilter(evalElement.dataSet); + if (fileFilter.accept(evalElement.contentNode)) { String childPath = - parentPath + FtpConstants.FILE_SEPARATOR - + evaluationResult.evaluatedTemplate; + parentPath + FtpConstants.FILE_SEPARATOR + evalElement.evaluatedTemplate; FtpFile childFtpFile = - new HierarchicalContentToFtpFileAdapter(childPath, - evaluationResult.contentNode, fileFilter); + FtpFileFactory.createFtpFile(evalElement.dataSet.getCode(), childPath, + evalElement.contentNode, fileFilter); result.add(childFtpFile); } } + return result; } - private List<EvaluatedDataSetPath> evaluateDataSetPaths(List<ExternalData> dataSets) + 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) { - List<EvaluatedDataSetPath> result = new ArrayList<EvaluatedDataSetPath>(); + FtpFileEvaluationContext evalContext = new FtpFileEvaluationContext(); for (int disambiguationIdx = 0; disambiguationIdx < dataSets.size(); disambiguationIdx++) { ExternalData dataSet = dataSets.get(disambiguationIdx); try { - List<EvaluatedDataSetPath> paths = evaluateDataSetPaths(dataSet, disambiguationIdx); - result.addAll(paths); + IHierarchicalContent hierarchicalContent = + evalContext.getHierarchicalContent(dataSet.getCode()); + IHierarchicalContentNode rootNode = + getDataSetFileListRoot(dataSet, hierarchicalContent); + List<EvaluatedElement> paths = + evaluateDataSetPaths(dataSet, rootNode, disambiguationIdx); + + evalContext.addEvaluatedElements(paths); } catch (Throwable t) { operationLog.warn("Failed to evaluate data set paths for dataset " @@ -316,41 +325,38 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, } } - return result; + return evalContext; } - private List<EvaluatedDataSetPath> evaluateDataSetPaths(ExternalData dataSet, - int disambiguationIndex) + private List<EvaluatedElement> evaluateDataSetPaths(ExternalData dataSet, + IHierarchicalContentNode rootNode, int disambiguationIndex) { - List<EvaluatedDataSetPath> result = new ArrayList<EvaluatedDataSetPath>(); - - IHierarchicalContentNode rootNode = getDataSetFileListRoot(dataSet); + List<EvaluatedElement> result = new ArrayList<EvaluatedElement>(); String disambiguationVar = computeDisambiguation(disambiguationIndex); for (IHierarchicalContentNode fileNode : getFileNamesRequiredByTemplate(rootNode)) { - EvaluatedDataSetPath evaluatedPath = new EvaluatedDataSetPath(); - evaluatedPath.dataSet = dataSet; - evaluatedPath.fileName = fileNode.getRelativePath(); - evaluatedPath.evaluatedTemplate = + EvaluatedElement evalElement = new EvaluatedElement(); + evalElement.dataSet = dataSet; + evalElement.pathInDataSet = fileNode.getRelativePath(); + evalElement.evaluatedTemplate = evaluateTemplate(dataSet, fileNode.getName(), disambiguationVar); - evaluatedPath.contentNode = fileNode; - result.add(evaluatedPath); + evalElement.contentNode = fileNode; + result.add(evalElement); } return result; } - private IHierarchicalContentNode getDataSetFileListRoot(ExternalData dataSet) + private IHierarchicalContentNode getDataSetFileListRoot(ExternalData dataSet, + IHierarchicalContent hierachicalContent) { - IHierarchicalContentProvider provider = ServiceProvider.getHierarchicalContentProvider(); - IHierarchicalContent content = provider.asContent(dataSet.getCode()); String fileListSubPathOrNull = getFileListSubPath(dataSet); if (false == StringUtils.isBlank(fileListSubPathOrNull)) { List<IHierarchicalContentNode> matchingNodes = - content.listMatchingNodes(fileListSubPathOrNull); + hierachicalContent.listMatchingNodes(fileListSubPathOrNull); if (false == matchingNodes.isEmpty()) { if (matchingNodes.size() == 1) @@ -373,7 +379,7 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, operationLog.warn(message); } } - return content.getRootNode(); + return hierachicalContent.getRootNode(); } /** @@ -434,6 +440,35 @@ public class TemplateBasedDataSetResourceResolver implements IFtpPathResolver, return parsedTemplate.getPlaceholderNames().contains(variableName); } + private Map<String, DataSetTypeConfig> initializeDataSetTypeConfigs( + FtpServerConfig ftpServerConfig) + { + Map<String, DataSetTypeConfig> result = new HashMap<String, DataSetTypeConfig>(); + Map<String, String> fileListSubPaths = ftpServerConfig.getFileListSubPaths(); + Map<String, String> fileListFilters = ftpServerConfig.getFileListFilters(); + + for (Entry<String, String> subPathEntry : fileListSubPaths.entrySet()) + { + DataSetTypeConfig dsConfig = new DataSetTypeConfig(); + dsConfig.fileListSubPath = subPathEntry.getValue(); + result.put(subPathEntry.getKey(), dsConfig); + } + + for (Entry<String, String> filterEntry : fileListFilters.entrySet()) + { + String dataSetType = filterEntry.getKey(); + String fileFilterPattern = filterEntry.getValue(); + DataSetTypeConfig dsConfig = result.get(dataSetType); + if (dsConfig == null) + { + dsConfig = new DataSetTypeConfig(); + } + dsConfig.fileFilter = createFilter(fileFilterPattern); + result.put(dataSetType, dsConfig); + } + return result; + } + private IHierarchicalContentNodeFilter createFilter(final String fileFilterPattern) { return new IHierarchicalContentNodeFilter() -- GitLab