From 2432ff678be30cf03f73f9849c96957bbfccbd99 Mon Sep 17 00:00:00 2001
From: cramakri <cramakri>
Date: Tue, 19 Feb 2013 10:18:49 +0000
Subject: [PATCH] BIS-329 SP-500 : Introducing a user-defined validity duration
 to access streams via URLs

SVN: 28389
---
 .../generic/server/AbstractDssServiceRpc.java |  5 ++-
 .../dss/generic/server/IStreamRepository.java |  8 ++--
 .../dss/generic/server/StreamRepository.java  | 32 +++++++++------
 .../server/api/v1/DssServiceRpcGeneric.java   | 25 +++++++++--
 .../api/v1/DssServiceRpcGenericLogger.java    | 24 +++++++++++
 .../shared/api/v1/IDssServiceRpcGeneric.java  | 37 +++++++++++++++++
 .../client/api/v1/impl/DssComponentTest.java  | 22 ++++++++++
 .../generic/server/StreamRepositoryTest.java  | 41 +++++++++++++++----
 8 files changed, 165 insertions(+), 29 deletions(-)

diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDssServiceRpc.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDssServiceRpc.java
index 7200100dd06..7729ee1a278 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDssServiceRpc.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/AbstractDssServiceRpc.java
@@ -141,11 +141,12 @@ public abstract class AbstractDssServiceRpc<T> extends AbstractServiceWithLogger
         return openBISService.tryGetDataSet(sessionToken, dataSetCode);
     }
 
-    protected String addToRepositoryAndReturnDownloadUrl(InputStream stream, String path)
+    protected String addToRepositoryAndReturnDownloadUrl(InputStream stream, String path,
+            long validityDurationInSeconds)
     {
         return downloadUrl + "/" + IdentifiedStreamHandlingServlet.SERVLET_NAME + "?"
                 + IdentifiedStreamHandlingServlet.STREAM_ID_PARAMETER_KEY + "="
-                + streamRepository.addStream(stream, path);
+                + streamRepository.addStream(stream, path, validityDurationInSeconds);
     }
 
 }
\ No newline at end of file
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/IStreamRepository.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/IStreamRepository.java
index 7ae604ce2f3..9f2a6c98a37 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/IStreamRepository.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/IStreamRepository.java
@@ -20,7 +20,7 @@ import java.io.InputStream;
 
 /**
  * Repositories for {@link InputStream} objects.
- *
+ * 
  * @author Franz-Josef Elmer
  */
 public interface IStreamRepository
@@ -28,8 +28,8 @@ public interface IStreamRepository
     /**
      * Adds specified stream and returns a unique id.
      */
-    public String addStream(InputStream inputStream, String path);
-    
+    public String addStream(InputStream inputStream, String path, long validityDurationInSeconds);
+
     /**
      * Retrieves stream by specified id. A stream can be retrieved only once.
      * 
@@ -37,5 +37,5 @@ public interface IStreamRepository
      *                           because the time between adding and retrieving was to long.
      */
     public InputStreamWithPath getStream(String inputStreamID);
-    
+
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepository.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepository.java
index 8ecaa64ec82..1e7594c3298 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepository.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepository.java
@@ -37,16 +37,20 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.IConfigProvider;
  */
 public class StreamRepository implements IStreamRepository
 {
-    private static final class InputStreamWithTimeStamp
+    private static final class InputStreamWithValidityDuration
     {
+        final long validityInMs;
+
         final Date timestamp;
 
         final InputStreamWithPath inputStreamWithPath;
 
-        InputStreamWithTimeStamp(InputStreamWithPath inputStreamWithPath, Date timestamp)
+        InputStreamWithValidityDuration(InputStreamWithPath inputStreamWithPath, Date timestamp,
+                long validityDuration)
         {
             this.inputStreamWithPath = inputStreamWithPath;
             this.timestamp = timestamp;
+            this.validityInMs = validityDuration;
         }
     }
 
@@ -66,9 +70,9 @@ public class StreamRepository implements IStreamRepository
         }
     }
 
-    private final Map<String, InputStreamWithTimeStamp> streams =
+    private final Map<String, InputStreamWithValidityDuration> streams =
 
-    new HashMap<String, InputStreamWithTimeStamp>();
+    new HashMap<String, InputStreamWithValidityDuration>();
 
     private final IUniqueIdGenerator inputStreamIDGenerator;
 
@@ -98,13 +102,16 @@ public class StreamRepository implements IStreamRepository
     }
 
     @Override
-    public synchronized String addStream(InputStream inputStream, String path)
+    public synchronized String addStream(InputStream inputStream, String path, long validityInSeconds)
     {
         removeStaleInputStreams();
         String id = inputStreamIDGenerator.createUniqueID();
         Date timestamp = new Date(timeProvider.getTimeInMilliseconds());
-        streams.put(id, new InputStreamWithTimeStamp(new InputStreamWithPath(inputStream, path),
-                timestamp));
+
+        long validityInMs = validityInSeconds * 1000L;
+        long validity = (validityInMs < minimumTime) ? minimumTime : validityInMs;
+        streams.put(id, new InputStreamWithValidityDuration(new InputStreamWithPath(inputStream,
+                path), timestamp, validity));
         return id;
     }
 
@@ -112,7 +119,7 @@ public class StreamRepository implements IStreamRepository
     public synchronized InputStreamWithPath getStream(String inputStreamID)
     {
         removeStaleInputStreams();
-        InputStreamWithTimeStamp inputStreamWithTimeStamp = streams.remove(inputStreamID);
+        InputStreamWithValidityDuration inputStreamWithTimeStamp = streams.remove(inputStreamID);
         if (inputStreamWithTimeStamp == null)
         {
             throw new IllegalArgumentException("Stream " + inputStreamID
@@ -124,11 +131,12 @@ public class StreamRepository implements IStreamRepository
     private void removeStaleInputStreams()
     {
         long currentTime = timeProvider.getTimeInMilliseconds();
-        Set<Entry<String, InputStreamWithTimeStamp>> entrySet = streams.entrySet();
-        for (Iterator<Entry<String, InputStreamWithTimeStamp>> iterator = entrySet.iterator(); iterator
-                .hasNext();)
+        Set<Entry<String, InputStreamWithValidityDuration>> entrySet = streams.entrySet();
+        for (Iterator<Entry<String, InputStreamWithValidityDuration>> iterator =
+                entrySet.iterator(); iterator.hasNext();)
         {
-            if (iterator.next().getValue().timestamp.getTime() < currentTime - minimumTime)
+            InputStreamWithValidityDuration stream = iterator.next().getValue();
+            if (stream.timestamp.getTime() < currentTime - stream.validityInMs)
             {
                 iterator.remove();
             }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGeneric.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGeneric.java
index 9f00a3d8fe7..ea06a9f443d 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGeneric.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGeneric.java
@@ -193,7 +193,16 @@ public class DssServiceRpcGeneric extends AbstractDssServiceRpc<IDssServiceRpcGe
             String path) throws IOExceptionUnchecked, IllegalArgumentException
     {
         InputStream stream = getFileForDataSet(sessionToken, dataSetCode, path);
-        return addToRepositoryAndReturnDownloadUrl(stream, path);
+        return addToRepositoryAndReturnDownloadUrl(stream, path, 0);
+    }
+
+    @Override
+    public String getDownloadUrlForFileForDataSetWithTimeout(String sessionToken,
+            String dataSetCode, String path, long validityDurationInSeconds)
+            throws IOExceptionUnchecked, IllegalArgumentException
+    {
+        InputStream stream = getFileForDataSet(sessionToken, dataSetCode, path);
+        return addToRepositoryAndReturnDownloadUrl(stream, path, validityDurationInSeconds);
     }
 
     private IHierarchicalContentNode getContentNode(IHierarchicalContent content, String startPath)
@@ -311,7 +320,7 @@ public class DssServiceRpcGeneric extends AbstractDssServiceRpc<IDssServiceRpcGe
     @Override
     public int getMinorVersion()
     {
-        return 6;
+        return 7;
     }
 
     /**
@@ -347,7 +356,17 @@ public class DssServiceRpcGeneric extends AbstractDssServiceRpc<IDssServiceRpcGe
             throws IOExceptionUnchecked, IllegalArgumentException
     {
         InputStream stream = getFileForDataSet(sessionToken, fileOrFolder);
-        return addToRepositoryAndReturnDownloadUrl(stream, fileOrFolder.getPath());
+        return addToRepositoryAndReturnDownloadUrl(stream, fileOrFolder.getPath(), 0);
+    }
+
+    @Override
+    public String getDownloadUrlForFileForDataSetWithTimeout(String sessionToken,
+            DataSetFileDTO fileOrFolder, long validityDurationInSeconds)
+            throws IOExceptionUnchecked, IllegalArgumentException
+    {
+        InputStream stream = getFileForDataSet(sessionToken, fileOrFolder);
+        return addToRepositoryAndReturnDownloadUrl(stream, fileOrFolder.getPath(),
+                validityDurationInSeconds);
     }
 
     @Override
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGenericLogger.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGenericLogger.java
index e74b35860dc..6d4fb716cf3 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGenericLogger.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/api/v1/DssServiceRpcGenericLogger.java
@@ -23,7 +23,9 @@ import java.util.Map;
 
 import ch.systemsx.cisd.base.exceptions.IOExceptionUnchecked;
 import ch.systemsx.cisd.openbis.common.spring.IInvocationLoggerContext;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.AuthorizationGuard;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.DataSetAccessGuard;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.DataSetFileDTOPredicate;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.IDssServiceRpcGenericInternal;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.DataSetFileDTO;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.FileInfoDssDTO;
@@ -206,4 +208,26 @@ public class DssServiceRpcGenericLogger extends AbstractServerLogger implements
         logAccess(sessionToken, "list-table-report-descriptions");
         return null;
     }
+
+    @Override
+    public String getDownloadUrlForFileForDataSetWithTimeout(String sessionToken,
+            String dataSetCode, String path, long validityDurationInSeconds)
+            throws IOExceptionUnchecked, IllegalArgumentException
+    {
+        logAccess(sessionToken, "get_download_url_for_file_for_data_set",
+                "DATA_SET(%s) PATH(%s) VALIDITY(%i)", dataSetCode, path, validityDurationInSeconds);
+        return null;
+    }
+
+    @Override
+    @DataSetAccessGuard
+    public String getDownloadUrlForFileForDataSetWithTimeout(
+            String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetFileDTOPredicate.class) DataSetFileDTO fileOrFolder,
+            long validityDurationInSeconds) throws IOExceptionUnchecked, IllegalArgumentException
+    {
+        logAccess(sessionToken, "get_download_url_for_file_for_data_set",
+                "DATA_SET(%s) VALIDITY(%i)", fileOrFolder, validityDurationInSeconds);
+        return null;
+    }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/api/v1/IDssServiceRpcGeneric.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/api/v1/IDssServiceRpcGeneric.java
index b21542d396a..745f02c7070 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/api/v1/IDssServiceRpcGeneric.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/shared/api/v1/IDssServiceRpcGeneric.java
@@ -86,6 +86,24 @@ public interface IDssServiceRpcGeneric extends IRpcService
             @AuthorizationGuard(guardClass = DataSetFileDTOPredicate.class) DataSetFileDTO fileOrFolder)
             throws IOExceptionUnchecked, IllegalArgumentException;
 
+    /**
+     * Returns an URL from which the requested file. The URL is valid for a caller-specified amount
+     * of time.
+     * 
+     * @param sessionToken The session token
+     * @param fileOrFolder The file or folder to retrieve
+     * @param validityDurationInSeconds The number of seconds for which the download URL should be
+     *            valid.
+     * @throws IOExceptionUnchecked Thrown if an IOException occurs when listing the files
+     * @throws IllegalArgumentException Thrown if the dataSetCode or startPath are not valid
+     * @since 1.7
+     */
+    @DataSetAccessGuard
+    public String getDownloadUrlForFileForDataSetWithTimeout(
+            String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetFileDTOPredicate.class) DataSetFileDTO fileOrFolder,
+            long validityDurationInSeconds) throws IOExceptionUnchecked, IllegalArgumentException;
+
     /**
      * Get an array of FileInfoDss objects that describe the file-system structure of the data set.
      * 
@@ -133,6 +151,25 @@ public interface IDssServiceRpcGeneric extends IRpcService
             @AuthorizationGuard(guardClass = DataSetCodeStringPredicate.class) String dataSetCode,
             String path) throws IOExceptionUnchecked, IllegalArgumentException;
 
+    /**
+     * Returns an URL from which the requested file of the specified data set can be downloaded. The
+     * URL is valid for a caller-specified amount of time.
+     * 
+     * @param sessionToken The session token
+     * @param dataSetCode The data set to retrieve file from
+     * @param path The path within the data set to retrieve file information about
+     * @param validityDurationInSeconds The number of seconds for which the download URL should be
+     *            valid.
+     * @throws IOExceptionUnchecked Thrown if an IOException occurs when listing the files
+     * @throws IllegalArgumentException Thrown if the dataSetCode or startPath are not valid
+     * @since 1.7
+     */
+    @DataSetAccessGuard
+    public String getDownloadUrlForFileForDataSetWithTimeout(String sessionToken,
+            @AuthorizationGuard(guardClass = DataSetCodeStringPredicate.class) String dataSetCode,
+            String path, long validityDurationInSeconds) throws IOExceptionUnchecked,
+            IllegalArgumentException;
+
     /**
      * Upload a new data set to the DSS.
      * 
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/DssComponentTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/DssComponentTest.java
index 957e295b877..9c8f222c43a 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/DssComponentTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/DssComponentTest.java
@@ -61,6 +61,9 @@ import ch.systemsx.cisd.openbis.dss.generic.server.api.v1.DssServiceRpcGeneric;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService;
 import ch.systemsx.cisd.openbis.dss.generic.shared.IShareIdManager;
 import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProviderTestWrapper;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.AuthorizationGuard;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.DataSetAccessGuard;
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.DataSetFileDTOPredicate;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.DssSessionAuthorizationHolder;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.authorization.IDssServiceRpcGenericInternal;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.DataSetFileDTO;
@@ -713,6 +716,25 @@ public class DssComponentTest extends AbstractFileSystemTestCase
         {
             return null;
         }
+
+        @Override
+        public String getDownloadUrlForFileForDataSetWithTimeout(String sessionToken,
+                String dataSetCode, String path, long validityDuration)
+                throws IOExceptionUnchecked, IllegalArgumentException
+        {
+            return url.toString();
+        }
+
+        @Override
+        @DataSetAccessGuard
+        public String getDownloadUrlForFileForDataSetWithTimeout(
+                String sessionToken,
+                @AuthorizationGuard(guardClass = DataSetFileDTOPredicate.class) DataSetFileDTO fileOrFolder,
+                long validityDurationInSeconds) throws IOExceptionUnchecked,
+                IllegalArgumentException
+        {
+            return url.toString();
+        }
     }
 
     private class MockDssServiceRpcV1_1 extends MockDssServiceRpcV1_0
diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepositoryTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepositoryTest.java
index 8bf1cd7ae5b..f8b033c4a95 100644
--- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepositoryTest.java
+++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/StreamRepositoryTest.java
@@ -66,9 +66,9 @@ public class StreamRepositoryTest extends AssertJUnit
     {
         StreamRepository repository = new StreamRepository(2, idGenerator, timeProvider);
         ByteArrayInputStream stream1 = new ByteArrayInputStream("s1".getBytes());
-        String id1 = repository.addStream(stream1, "f1.txt");
+        String id1 = repository.addStream(stream1, "f1.txt", 0);
         ByteArrayInputStream stream2 = new ByteArrayInputStream("s2".getBytes());
-        String id2 = repository.addStream(stream2, "f2.txt");
+        String id2 = repository.addStream(stream2, "f2.txt", 0);
 
         assertEquals("0", id1);
         assertEquals("1", id2);
@@ -86,10 +86,10 @@ public class StreamRepositoryTest extends AssertJUnit
     {
         StreamRepository repository = new StreamRepository(2, idGenerator, timeProvider);
         ByteArrayInputStream stream1 = new ByteArrayInputStream("s1".getBytes());
-        String id1 = repository.addStream(stream1, "f1.txt");
-        
+        String id1 = repository.addStream(stream1, "f1.txt", 0);
+
         assertEquals("0", id1);
-        
+
         assertSame(stream1, repository.getStream("0").getInputStream());
         try
         {
@@ -100,15 +100,15 @@ public class StreamRepositoryTest extends AssertJUnit
             assertEquals("Stream 0 is no longer available.", ex.getMessage());
         }
     }
-    
+
     @Test
     public void testAddingAndRetrievingTwoStreamsButSecondStreamNoLongerExists()
     {
         StreamRepository repository = new StreamRepository(2, idGenerator, timeProvider);
         ByteArrayInputStream stream1 = new ByteArrayInputStream("s1".getBytes());
-        String id1 = repository.addStream(stream1, "f1.txt");
+        String id1 = repository.addStream(stream1, "f1.txt", 0);
         ByteArrayInputStream stream2 = new ByteArrayInputStream("s2".getBytes());
-        String id2 = repository.addStream(stream2, "f2.txt");
+        String id2 = repository.addStream(stream2, "f2.txt", 0);
 
         assertEquals("0", id1);
         assertEquals("1", id2);
@@ -127,4 +127,29 @@ public class StreamRepositoryTest extends AssertJUnit
         }
     }
 
+    @Test
+    public void testValidityDuration()
+    {
+        StreamRepository repository = new StreamRepository(2, idGenerator, timeProvider);
+        ByteArrayInputStream stream0 = new ByteArrayInputStream("s1".getBytes());
+        repository.addStream(stream0, "f1.txt", 5);
+        ByteArrayInputStream stream1 = new ByteArrayInputStream("s2".getBytes());
+        repository.addStream(stream1, "f2.txt", 3);
+
+        // Advance to a time when stream 1 is still available but not stream2
+        timeProvider.getTimeInMilliseconds();
+        timeProvider.getTimeInMilliseconds();
+        timeProvider.getTimeInMilliseconds();
+        timeProvider.getTimeInMilliseconds();
+        timeProvider.getTimeInMilliseconds();
+        assertSame(stream0, repository.getStream("0").getInputStream());
+        try
+        {
+            repository.getStream("1");
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex)
+        {
+            assertEquals("Stream 1 is no longer available.", ex.getMessage());
+        }
+    }
 }
-- 
GitLab