From 003be72d9df4d87fd68eb97bb17d85da915ffda3 Mon Sep 17 00:00:00 2001
From: jakubs <jakubs>
Date: Tue, 2 Aug 2016 13:54:52 +0000
Subject: [PATCH] SSDM-3907: create configurable plugins for new ftp / cifs
 file structure

SVN: 36862
---
 .../generic/server/cifs/CifsServerConfig.java | 22 ++---
 .../server/ftp/FtpPathResolverConfig.java     |  9 ++
 .../ftp/v3/V3ExperimentLevelResolver.java     | 16 ++--
 .../server/ftp/v3/V3FileSystemPlugin.java     | 60 +++++++++++++
 .../ftp/v3/V3FtpPathResolverRegistry.java     | 86 ++++++++++++++++++-
 .../ftp/v3/V3HierarchicalContentResolver.java |  7 +-
 .../server/ftp/v3/V3PluginResolver.java       | 46 +++++-----
 .../server/ftp/v3/V3ProjectLevelResolver.java | 14 +--
 .../dss/generic/server/ftp/v3/V3Resolver.java | 20 +----
 .../server/ftp/v3/V3RootLevelResolver.java    | 22 ++---
 .../server/ftp/v3/V3SpaceLevelResolver.java   | 13 ++-
 .../openbis/dss/generic/shared/Constants.java |  3 +
 .../generic/shared/utils/DssPluginType.java   |  1 +
 13 files changed, 222 insertions(+), 97 deletions(-)
 create mode 100644 datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FileSystemPlugin.java

diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/cifs/CifsServerConfig.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/cifs/CifsServerConfig.java
index 7d995c85f27..cf3d2d41bc0 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/cifs/CifsServerConfig.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/cifs/CifsServerConfig.java
@@ -34,6 +34,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.utils.DssPropertyParametersUt
 class CifsServerConfig
 {
     private static final String SECTION_NAME = "cifs.server";
+
     private static final Template CONFIG_TEMPLATE_XML = new Template("<jlanserver>\n"
             + "<servers><SMB/></servers>\n"
             + "<SMB>\n"
@@ -63,9 +64,9 @@ class CifsServerConfig
             + "</debug>\n"
             + "<security>\n"
             + "  <JCEProvider>org.bouncycastle.jce.provider.BouncyCastleProvider</JCEProvider>\n"
-            + "  <usersInterface>\n" 
-            + "    <class>ch.systemsx.cisd.openbis.dss.generic.server.cifs.DummyUsersInterface</class>\n" 
-            + "  </usersInterface>\n"  
+            + "  <usersInterface>\n"
+            + "    <class>ch.systemsx.cisd.openbis.dss.generic.server.cifs.DummyUsersInterface</class>\n"
+            + "  </usersInterface>\n"
             + "</security>\n"
             + "</jlanserver>\n");
 
@@ -81,16 +82,17 @@ class CifsServerConfig
             .entry("log-level", "INFO")
             .entry("share-name", "STORE")
             .getMap();
-    
+
     static Properties getServerProperties()
     {
         return PropertyParametersUtil.extractSingleSectionProperties(
-                DssPropertyParametersUtil.loadServiceProperties(), SECTION_NAME, false).getProperties();
+                DssPropertyParametersUtil.loadServiceProperties(), SECTION_NAME, true).getProperties();
     }
-    
+
     private final boolean enabled;
+
     private final Properties serverProperties;
-    
+
     public CifsServerConfig(Properties props)
     {
         serverProperties = PropertyParametersUtil.extractSingleSectionProperties(props, SECTION_NAME, false).getProperties();
@@ -101,12 +103,12 @@ class CifsServerConfig
     {
         return enabled;
     }
-    
+
     public String getPort()
     {
         return getProperty(SMB_PORT_KEY);
     }
-    
+
     private String getProperty(String key)
     {
         return serverProperties.getProperty(key, CONFIG_PARAMS.get(key));
@@ -122,5 +124,5 @@ class CifsServerConfig
         }
         return template.createText();
     }
-    
+
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverConfig.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverConfig.java
index 54a36fddf97..9797220c52b 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverConfig.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/FtpPathResolverConfig.java
@@ -63,6 +63,8 @@ public class FtpPathResolverConfig
 
     private final String resolverClass;
 
+    private final Properties properties;
+
     public FtpPathResolverConfig(Properties props)
     {
         dataSetDisplayTemplate =
@@ -71,6 +73,8 @@ public class FtpPathResolverConfig
 
         resolverClass = PropertyUtils.getProperty(props, PATH_RESOLVER_KEY, V3FtpPathResolverRegistry.class.getCanonicalName());
 
+        properties = props;
+
         ExtendedProperties fileListSubPathProps =
                 ExtendedProperties.getSubset(props, DATASET_FILELIST_SUBPATH_KEY, true);
         for (Object key : fileListSubPathProps.keySet())
@@ -132,6 +136,11 @@ public class FtpPathResolverConfig
         }
     }
 
+    public Properties getProperties()
+    {
+        return properties;
+    }
+
     public IFtpPathResolverRegistry getResolverRegistry()
     {
         try
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ExperimentLevelResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ExperimentLevelResolver.java
index a8dc97b517c..c69223d7085 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ExperimentLevelResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ExperimentLevelResolver.java
@@ -29,25 +29,25 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 
-class V3ExperimentLevelResolver extends V3Resolver
+class V3ExperimentLevelResolver implements V3Resolver
 {
     private IExperimentId experimentId;
 
-    public V3ExperimentLevelResolver(IExperimentId experimentId, FtpPathResolverContext resolverContext)
+    public V3ExperimentLevelResolver(IExperimentId experimentId)
     {
-        super(resolverContext);
         this.experimentId = experimentId;
     }
 
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         if (subPath.length == 0)
         {
             ExperimentFetchOptions fetchOptions = new ExperimentFetchOptions();
             fetchOptions.withDataSets();
 
-            Map<IExperimentId, Experiment> experiments = api.getExperiments(sessionToken, Collections.singletonList(experimentId), fetchOptions);
+            Map<IExperimentId, Experiment> experiments =
+                    context.getV3Api().getExperiments(context.getSessionToken(), Collections.singletonList(experimentId), fetchOptions);
             Experiment exp = experiments.get(experimentId);
 
             V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
@@ -59,10 +59,10 @@ class V3ExperimentLevelResolver extends V3Resolver
         } else
         {
             String dataSetCode = subPath[0];
-            IHierarchicalContent content = resolverContext.getContentProvider().asContent(dataSetCode);
+            IHierarchicalContent content = context.getContentProvider().asContent(dataSetCode);
             String[] remaining = Arrays.copyOfRange(subPath, 1, subPath.length);
-            V3HierarchicalContentResolver resolver = new V3HierarchicalContentResolver(content, resolverContext);
-            return resolver.resolve(fullPath, remaining);
+            V3HierarchicalContentResolver resolver = new V3HierarchicalContentResolver(content);
+            return resolver.resolve(fullPath, remaining, context);
         }
     }
 
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FileSystemPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FileSystemPlugin.java
new file mode 100644
index 00000000000..83499ed6d06
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FileSystemPlugin.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 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.v3;
+
+/**
+ * @author Jakub Straszewski
+ */
+public class V3FileSystemPlugin
+{
+    public final String pluginCode;
+
+    public final Class<?> pluginResolverClass;
+
+    public V3FileSystemPlugin(String pluginCode, Class<?> pluginResolverClass)
+    {
+        this.pluginCode = pluginCode;
+        this.pluginResolverClass = pluginResolverClass;
+        try
+        {
+            // cast to test if the instantiation can be done and is creating an object of a proper type
+            @SuppressWarnings("unused")
+            V3Resolver result = (V3Resolver) pluginResolverClass.newInstance();
+        } catch (InstantiationException | IllegalAccessException ex)
+        {
+            throw new IllegalStateException("Couldn't instantiate object of type " + pluginResolverClass);
+        }
+    }
+
+    public String getPluginCode()
+    {
+        return pluginCode;
+    }
+
+    public V3Resolver getPluginResolver()
+    {
+        try
+        {
+            return (V3Resolver) pluginResolverClass.newInstance();
+        } catch (InstantiationException | IllegalAccessException ex)
+        {
+            // this should be impossible as before creating this the check should have already been done.
+            throw new IllegalStateException("Couldn't instantiate object of type " + pluginResolverClass);
+        }
+    }
+
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FtpPathResolverRegistry.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FtpPathResolverRegistry.java
index 19adef5b964..1b4b2c98b04 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FtpPathResolverRegistry.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3FtpPathResolverRegistry.java
@@ -16,15 +16,23 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3;
 
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
 import org.apache.ftpserver.ftplet.FtpFile;
 import org.apache.log4j.Logger;
 
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverConfig;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.IFtpPathResolverRegistry;
+import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpNonExistingFile;
+import ch.systemsx.cisd.openbis.dss.generic.shared.Constants;
 
 /**
  * A registry of ftp resolvers. It keeps the style of old-style resolver regisrty, but actually only calls itself root resolver.
@@ -37,10 +45,39 @@ public class V3FtpPathResolverRegistry implements IFtpPathResolverRegistry
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION,
             V3FtpPathResolverRegistry.class);
 
+    private List<V3FileSystemPlugin> plugins = new LinkedList<>();
+
     @Override
     public void initialize(FtpPathResolverConfig config)
     {
+        Properties props = config.getProperties();
+        String listOfEnabledFileSystemPlugins = props.getProperty(Constants.DSS_FS_PLUGIN_NAMES);
+
+        if (listOfEnabledFileSystemPlugins != null)
+        {
+            String[] split = listOfEnabledFileSystemPlugins.split(",");
+            for (String pluginName : split)
+            {
+                pluginName = pluginName.trim();
+                String className = props.getProperty(pluginName + ".resolver-class");
+                String code = props.getProperty(pluginName + ".code");
+                try
+                {
+                    Class<?> clazz = Class.forName(className);
+                    plugins.add(new V3FileSystemPlugin(code, clazz));
+                } catch (ClassNotFoundException ex)
+                {
+                    throw new ConfigurationFailureException("Couldn't load class for file system plugin");
+                } catch (SecurityException ex)
+                {
+                    throw new ConfigurationFailureException("Couldn't load class for file system plugin");
+                } catch (Exception ex)
+                {
+                    throw new ConfigurationFailureException("Couldn't load class for file system plugin", ex);
+                }
 
+            }
+        }
     }
 
     // note to self - the resolver context is created fresh for each request
@@ -48,11 +85,16 @@ public class V3FtpPathResolverRegistry implements IFtpPathResolverRegistry
     public FtpFile resolve(String path, FtpPathResolverContext resolverContext)
     {
         System.err.println(path + " Resolver registry: " + this.hashCode());
+        String[] split = path.equals("/") ? new String[] {} : path.substring(1).split("/");
         try
         {
-            V3RootLevelResolver resolver = new V3RootLevelResolver(resolverContext);
-            String[] split = path.equals("/") ? new String[] {} : path.substring(1).split("/");
-            return resolver.resolve(path, split);
+            if (plugins.size() > 0)
+            {
+                return resolvePlugins(path, split, resolverContext);
+            } else
+            {
+                return resolveDefault(path, resolverContext, split);
+            }
         } catch (Exception e)
         {
             operationLog.warn(e);
@@ -60,4 +102,42 @@ public class V3FtpPathResolverRegistry implements IFtpPathResolverRegistry
         return new V3FtpNonExistingFile(path, "Error when retrieving path");
     }
 
+    private FtpFile resolveDefault(String path, FtpPathResolverContext resolverContext, String[] split)
+    {
+        V3RootLevelResolver resolver = new V3RootLevelResolver();
+        return resolver.resolve(path, split, resolverContext);
+    }
+
+    private FtpFile resolvePlugins(String path, String[] subPath, FtpPathResolverContext resolverContext)
+    {
+        if (subPath.length == 0)
+        {
+            V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(path);
+            response.addDirectory("DEFAULT");
+            for (V3FileSystemPlugin plugin : plugins)
+            {
+                response.addDirectory(plugin.getPluginCode());
+            }
+            return response;
+        } else
+        {
+            String[] remaining = Arrays.copyOfRange(subPath, 1, subPath.length);
+            if (subPath[0].equals("DEFAULT"))
+            {
+                return resolveDefault(path, resolverContext, subPath);
+            } else
+            {
+                for (V3FileSystemPlugin plugin : plugins)
+                {
+                    if (plugin.getPluginCode().equals(subPath[0]))
+                    {
+                        V3Resolver resolver = plugin.getPluginResolver();
+                        return resolver.resolve(path, remaining, resolverContext);
+                    }
+                }
+                return new V3FtpNonExistingFile(path, "Error when retrieving path");
+            }
+        }
+    }
+
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3HierarchicalContentResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3HierarchicalContentResolver.java
index 4a7535838b4..b49e028a0f4 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3HierarchicalContentResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3HierarchicalContentResolver.java
@@ -24,19 +24,18 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryRes
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFileResponse;
 
-class V3HierarchicalContentResolver extends V3Resolver
+class V3HierarchicalContentResolver implements V3Resolver
 {
 
     private IHierarchicalContent content;
 
-    public V3HierarchicalContentResolver(IHierarchicalContent content, FtpPathResolverContext resolverContext)
+    public V3HierarchicalContentResolver(IHierarchicalContent content)
     {
-        super(resolverContext);
         this.content = content;
     }
 
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         IHierarchicalContentNode rootNode;
         if (subPath.length == 0)
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3PluginResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3PluginResolver.java
index aa4a4779ab8..2b20c09cde0 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3PluginResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3PluginResolver.java
@@ -35,33 +35,29 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryRes
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFileResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpNonExistingFile;
+import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider;
 
-class V3PluginResolver extends V3Resolver
+class V3PluginResolver implements V3Resolver
 {
 
-    public V3PluginResolver(FtpPathResolverContext resolverContext)
-    {
-        super(resolverContext);
-    }
-
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         if (subPath.length == 0)
         {
-            return listDataSetTypes(fullPath);
+            return listDataSetTypes(fullPath, context);
         }
 
         String dataSetType = subPath[0];
         if (subPath.length == 1)
         {
-            return listDataSetsOfGivenType(fullPath, dataSetType);
+            return listDataSetsOfGivenType(fullPath, dataSetType, context);
         }
 
-        return resolveFileSearch(fullPath, subPath);
+        return resolveFileSearch(fullPath, subPath, context);
     }
 
-    private V3FtpFile resolveFileSearch(String fullPath, String[] subPath)
+    private V3FtpFile resolveFileSearch(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         String dataSetCode = subPath[1];
         String requestedFileName = subPath.length == 2 ? null : subPath[2];
@@ -70,11 +66,11 @@ class V3PluginResolver extends V3Resolver
             throw new IllegalArgumentException("This resolver can't resolve path of that length");
         }
 
-        List<DataSet> dataSetsToSearch = searchForDataSetAndParents(dataSetCode);
+        List<DataSet> dataSetsToSearch = searchForDataSetAndParents(dataSetCode, context);
 
         if (requestedFileName != null)
         {
-            IHierarchicalContentNode result = findRequestedNode(dataSetsToSearch, requestedFileName);
+            IHierarchicalContentNode result = findRequestedNode(dataSetsToSearch, requestedFileName, context.getContentProvider());
             if (result != null)
             {
                 return new V3FtpFileResponse(fullPath, result);
@@ -85,20 +81,20 @@ class V3PluginResolver extends V3Resolver
         }
 
         V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
-        for (IHierarchicalContentNode file : findAllNodes(dataSetsToSearch))
+        for (IHierarchicalContentNode file : findAllNodes(dataSetsToSearch, context.getContentProvider()))
         {
             response.addFile(file.getName(), file);
         }
         return response;
     }
 
-    private List<IHierarchicalContentNode> findAllNodes(List<DataSet> dataSetsToSearch)
+    private List<IHierarchicalContentNode> findAllNodes(List<DataSet> dataSetsToSearch, IHierarchicalContentProvider contentProvider)
     {
         List<IHierarchicalContentNode> result = new ArrayList<>();
         for (DataSet dataSet : dataSetsToSearch)
         {
             Stack<IHierarchicalContentNode> nodes = new Stack<>();
-            nodes.push(resolverContext.getContentProvider().asContent(dataSet.getCode()).getRootNode());
+            nodes.push(contentProvider.asContent(dataSet.getCode()).getRootNode());
 
             while (false == nodes.isEmpty())
             {
@@ -118,11 +114,12 @@ class V3PluginResolver extends V3Resolver
         return result;
     }
 
-    private IHierarchicalContentNode findRequestedNode(List<DataSet> dataSetsToSearch, String requestedFileName)
+    private IHierarchicalContentNode findRequestedNode(List<DataSet> dataSetsToSearch, String requestedFileName,
+            IHierarchicalContentProvider contentProvider)
     {
         for (DataSet dataSet : dataSetsToSearch)
         {
-            IHierarchicalContent content = resolverContext.getContentProvider().asContent(dataSet.getCode());
+            IHierarchicalContent content = contentProvider.asContent(dataSet.getCode());
             IHierarchicalContentNode result = findRequestedNode(content.getRootNode(), requestedFileName);
             if (result != null)
             {
@@ -155,12 +152,12 @@ class V3PluginResolver extends V3Resolver
     }
 
     // returns a list of data sets starting with requested data set and continuedby it's parents
-    private List<DataSet> searchForDataSetAndParents(String dataSetCode)
+    private List<DataSet> searchForDataSetAndParents(String dataSetCode, FtpPathResolverContext context)
     {
         DataSetPermId dataId = new DataSetPermId(dataSetCode);
         DataSetFetchOptions fetchOptions = new DataSetFetchOptions();
         fetchOptions.withParents();
-        DataSet dataSet = api.getDataSets(sessionToken, Collections.singletonList(dataId), fetchOptions).get(dataId);
+        DataSet dataSet = context.getV3Api().getDataSets(context.getSessionToken(), Collections.singletonList(dataId), fetchOptions).get(dataId);
 
         List<DataSet> dataSetsToSearch = new ArrayList<>();
         dataSetsToSearch.add(dataSet);
@@ -168,13 +165,13 @@ class V3PluginResolver extends V3Resolver
         return dataSetsToSearch;
     }
 
-    private V3FtpFile listDataSetsOfGivenType(String fullPath, String dataSetType)
+    private V3FtpFile listDataSetsOfGivenType(String fullPath, String dataSetType, FtpPathResolverContext context)
     {
         DataSetFetchOptions fetchOptions = new DataSetFetchOptions();
         fetchOptions.withParents();
         DataSetSearchCriteria searchCriteria = new DataSetSearchCriteria();
         searchCriteria.withType().withCode().thatEquals(dataSetType);
-        List<DataSet> dataSets = api.searchDataSets(sessionToken, searchCriteria, fetchOptions).getObjects();
+        List<DataSet> dataSets = context.getV3Api().searchDataSets(context.getSessionToken(), searchCriteria, fetchOptions).getObjects();
 
         V3FtpDirectoryResponse result = new V3FtpDirectoryResponse(fullPath);
         for (DataSet dataSet : dataSets)
@@ -184,10 +181,11 @@ class V3PluginResolver extends V3Resolver
         return result;
     }
 
-    private V3FtpFile listDataSetTypes(String fullPath)
+    private V3FtpFile listDataSetTypes(String fullPath, FtpPathResolverContext context)
     {
         List<DataSetType> dataSetTypes =
-                api.searchDataSetTypes(sessionToken, new DataSetTypeSearchCriteria(), new DataSetTypeFetchOptions()).getObjects();
+                context.getV3Api().searchDataSetTypes(context.getSessionToken(), new DataSetTypeSearchCriteria(), new DataSetTypeFetchOptions())
+                        .getObjects();
 
         V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
         for (DataSetType type : dataSetTypes)
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ProjectLevelResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ProjectLevelResolver.java
index 4cab7883df9..e01a870260c 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ProjectLevelResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3ProjectLevelResolver.java
@@ -30,25 +30,25 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 
-class V3ProjectLevelResolver extends V3Resolver
+class V3ProjectLevelResolver implements V3Resolver
 {
     private ProjectIdentifier projectIdentifier;
 
-    public V3ProjectLevelResolver(String spaceCode, String projectCode, FtpPathResolverContext resolverContext)
+    public V3ProjectLevelResolver(String spaceCode, String projectCode)
     {
-        super(resolverContext);
         this.projectIdentifier = new ProjectIdentifier(spaceCode, projectCode);
     }
 
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         if (subPath.length == 0)
         {
             ProjectFetchOptions fetchOptions = new ProjectFetchOptions();
             fetchOptions.withExperiments();
 
-            Map<IProjectId, Project> projects = api.getProjects(sessionToken, Collections.singletonList(projectIdentifier), fetchOptions);
+            Map<IProjectId, Project> projects =
+                    context.getV3Api().getProjects(context.getSessionToken(), Collections.singletonList(projectIdentifier), fetchOptions);
             Project project = projects.get(projectIdentifier);
 
             V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
@@ -62,8 +62,8 @@ class V3ProjectLevelResolver extends V3Resolver
             String item = subPath[0];
             String[] remaining = Arrays.copyOfRange(subPath, 1, subPath.length);
             V3ExperimentLevelResolver resolver =
-                    new V3ExperimentLevelResolver(new ExperimentIdentifier(projectIdentifier.getIdentifier() + "/" + item), resolverContext);
-            return resolver.resolve(fullPath, remaining);
+                    new V3ExperimentLevelResolver(new ExperimentIdentifier(projectIdentifier.getIdentifier() + "/" + item));
+            return resolver.resolve(fullPath, remaining, context);
         }
     }
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3Resolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3Resolver.java
index dee0764e977..9f1a126b312 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3Resolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3Resolver.java
@@ -16,27 +16,13 @@
 
 package ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3;
 
-import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 
-public abstract class V3Resolver
+public interface V3Resolver
 {
-    protected FtpPathResolverContext resolverContext;
-
-    protected IApplicationServerApi api;
-
-    protected String sessionToken;
-
-    public V3Resolver(FtpPathResolverContext resolverContext)
-    {
-        this.resolverContext = resolverContext;
-        this.sessionToken = resolverContext.getSessionToken();
-        this.api = resolverContext.getV3Api();
-    }
-
     /**
      * Create a ftp file which has specified full path, resolving the local path specified as an array of path items.
      */
-    public abstract V3FtpFile resolve(String fullPath, String[] pathItems);
-}
\ No newline at end of file
+    public abstract V3FtpFile resolve(String fullPath, String[] pathItems, FtpPathResolverContext resolverContext);
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3RootLevelResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3RootLevelResolver.java
index 0b0ee76344b..340eef4a90a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3RootLevelResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3RootLevelResolver.java
@@ -26,42 +26,30 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 
-class V3RootLevelResolver extends V3Resolver
+class V3RootLevelResolver implements V3Resolver
 {
-    public V3RootLevelResolver(FtpPathResolverContext resolverContext)
-    {
-        super(resolverContext);
-    }
 
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         if (subPath.length == 0)
         {
             List<Space> spaces =
-                    api.searchSpaces(sessionToken, new SpaceSearchCriteria(), new SpaceFetchOptions()).getObjects();
+                    context.getV3Api().searchSpaces(context.getSessionToken(), new SpaceSearchCriteria(), new SpaceFetchOptions()).getObjects();
 
             V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
             for (Space space : spaces)
             {
                 response.addDirectory(space.getCode());
             }
-            response.addDirectory("__PLUGIN__");
             return response;
         } else
         {
             String item = subPath[0];
             String[] remaining = Arrays.copyOfRange(subPath, 1, subPath.length);
 
-            if (item.equals("__PLUGIN__"))
-            {
-                V3PluginResolver resolver = new V3PluginResolver(resolverContext);
-                return resolver.resolve(fullPath, remaining);
-            } else
-            {
-                V3SpaceLevelResolver resolver = new V3SpaceLevelResolver(item, resolverContext);
-                return resolver.resolve(fullPath, remaining);
-            }
+            V3SpaceLevelResolver resolver = new V3SpaceLevelResolver(item);
+            return resolver.resolve(fullPath, remaining, context);
         }
     }
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3SpaceLevelResolver.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3SpaceLevelResolver.java
index c88b00d8fb8..b55ee5cbc77 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3SpaceLevelResolver.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ftp/v3/V3SpaceLevelResolver.java
@@ -26,18 +26,17 @@ import ch.systemsx.cisd.openbis.dss.generic.server.ftp.FtpPathResolverContext;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpDirectoryResponse;
 import ch.systemsx.cisd.openbis.dss.generic.server.ftp.v3.file.V3FtpFile;
 
-class V3SpaceLevelResolver extends V3Resolver
+class V3SpaceLevelResolver implements V3Resolver
 {
     String spaceCode;
 
-    public V3SpaceLevelResolver(String spaceCode, FtpPathResolverContext resolverContext)
+    public V3SpaceLevelResolver(String spaceCode)
     {
-        super(resolverContext);
         this.spaceCode = spaceCode;
     }
 
     @Override
-    public V3FtpFile resolve(String fullPath, String[] subPath)
+    public V3FtpFile resolve(String fullPath, String[] subPath, FtpPathResolverContext context)
     {
         if (subPath.length == 0)
         {
@@ -45,7 +44,7 @@ class V3SpaceLevelResolver extends V3Resolver
             searchCriteria.withSpace().withCode().thatEquals(spaceCode);
             ProjectFetchOptions fetchOptions = new ProjectFetchOptions();
             List<Project> projects =
-                    api.searchProjects(sessionToken, searchCriteria, fetchOptions).getObjects();
+                    context.getV3Api().searchProjects(context.getSessionToken(), searchCriteria, fetchOptions).getObjects();
 
             V3FtpDirectoryResponse response = new V3FtpDirectoryResponse(fullPath);
             for (Project project : projects)
@@ -57,8 +56,8 @@ class V3SpaceLevelResolver extends V3Resolver
         {
             String item = subPath[0];
             String[] remaining = Arrays.copyOfRange(subPath, 1, subPath.length);
-            V3ProjectLevelResolver resolver = new V3ProjectLevelResolver(spaceCode, item, resolverContext);
-            return resolver.resolve(fullPath, remaining);
+            V3ProjectLevelResolver resolver = new V3ProjectLevelResolver(spaceCode, item);
+            return resolver.resolve(fullPath, remaining, context);
         }
     }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/Constants.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/Constants.java
index b6e332a6c1b..3f480f079bb 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/Constants.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/Constants.java
@@ -46,4 +46,7 @@ public class Constants
 
     public static String OVERVIEW_PLUGINS_SERVICES_LIST_KEY = "overview-plugins";
 
+    /** Plugins for file system views (ftp / cifs) */
+    public static final String DSS_FS_PLUGIN_NAMES = "file-system-plugins";
+
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DssPluginType.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DssPluginType.java
index 606ced76ebf..689c5c04ca2 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DssPluginType.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/utils/DssPluginType.java
@@ -33,6 +33,7 @@ public enum DssPluginType implements IPluginType
     PROCESSING_PLUGINS("processing-plugins", Constants.PROCESSING_PLUGIN_NAMES),
     SEARCH_DOMAIN_SERVICES("search-domain-services", Constants.SEARCH_DOMAIN_SERVICE_NAMES),
     MAINTENANCE_TASKS("maintenance-tasks", MaintenanceTaskUtils.DEFAULT_MAINTENANCE_PLUGINS_PROPERTY_NAME),
+    DSS_FS_PLUGINS("file-system-plugins", Constants.DSS_FS_PLUGIN_NAMES),
     MISCELLANEOUS("miscellaneous", null);
 
     private PluginType pluginType;
-- 
GitLab