diff --git a/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java b/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java index 09609ae77d..a0d8ec6165 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/DeletedNodes.java @@ -102,18 +102,30 @@ public interface DeletedNodes CollectionWithPagingInfo getRenditions(String archivedId, Parameters parameters); /** - * Gets a presigned URL to directly access content. + * Gets a presigned URL to directly access content. * - * @param archivedId The node id for which to obtain the direct access {@code URL} - * @param renditionId The rendition id for which to obtain the direct access {@code URL} - * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. - * @return A direct access {@code URL} object for the content. - */ + * @param archivedId The node id for which to obtain the direct access {@code URL} + * @param renditionId The rendition id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. + * @return A direct access {@code URL} object for the content. + */ default DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment) { return requestContentDirectUrl(archivedId, renditionId, attachment, null); } + /** + * @param archivedId The node id for which to obtain the direct access {@code URL} + * @param renditionId The rendition id for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. + * @param validFor The time at which the direct access {@code URL} will expire. + * @return A direct access {@code URL} object for the content. + */ + default DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment, Long validFor) + { + return requestContentDirectUrl(archivedId, renditionId, attachment, validFor, null); + } + /** * Gets a presigned URL to directly access content. * @@ -121,8 +133,9 @@ public interface DeletedNodes * @param renditionId The rendition id for which to obtain the direct access {@code URL} * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}, {@code true} by default. * @param validFor The time at which the direct access {@code URL} will expire. + * @param fileName Optional overide for file name * @return A direct access {@code URL} object for the content. */ - DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment, Long validFor); + DirectAccessUrl requestContentDirectUrl(String archivedId, String renditionId, boolean attachment, Long validFor, String fileName); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java index 93d49d3c93..163b9da1d1 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/DirectAccessUrlHelper.java @@ -63,4 +63,10 @@ public class DirectAccessUrlHelper } return attachment; } + + + public String getFileName(DirectAccessUrlRequest directAccessUrlRequest) + { + return directAccessUrlRequest != null ? directAccessUrlRequest.getFileName() : null; + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java index a0f527b441..25f3c10205 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Nodes.java @@ -323,7 +323,20 @@ public interface Nodes * @param validFor The time at which the direct access {@code URL} will expire. * @return A direct access {@code URL} object for the content. */ - DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor); + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + { + return requestContentDirectUrl(nodeRef, attachment, validFor, null); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef The node reference for which to obtain the direct access {@code URL} + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @param fileName Optional name for the file when downloaded + * @return A direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor, String fileName); /** * Convert from node properties (map of QName to Serializable) retrieved from diff --git a/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java b/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java index ebcece08ef..a1247d0a15 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/Renditions.java @@ -251,7 +251,6 @@ public interface Renditions } /** - * Gets a presigned URL to directly access content. * @param nodeRef the node reference for which to obtain the direct access {@code URL} * @param versionId the version id (aka version label) * @param renditionId the rendition id @@ -259,6 +258,21 @@ public interface Renditions * @param validFor the time at which the direct access {@code URL} will expire * @return a direct access {@code URL} object for the content. */ - DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor); + default DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor) + { + return requestContentDirectUrl(nodeRef, versionId, renditionId, attachment, validFor, null); + } + + /** + * Gets a presigned URL to directly access content. + * @param nodeRef the node reference for which to obtain the direct access {@code URL} + * @param versionId the version id (aka version label) + * @param renditionId the rendition id + * @param attachment {@code true} if an attachment {@code URL} is requested, {@code false} for an embedded {@code URL} + * @param validFor the time at which the direct access {@code URL} will expire + * @param fileName optional name for the file when downloaded + * @return a direct access {@code URL} object for the content. + */ + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor, String fileName); } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java index f63de476e2..aa6a7bc2f5 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/DeletedNodesImpl.java @@ -250,18 +250,18 @@ public class DeletedNodesImpl implements DeletedNodes, RecognizedParamsExtractor * {@inheritDoc} */ @Override - public DirectAccessUrl requestContentDirectUrl(String originalNodeId, String renditionId, boolean attachment, Long validFor) + public DirectAccessUrl requestContentDirectUrl(String originalNodeId, String renditionId, boolean attachment, Long validFor, String fileName) { //First check the node is valid and has been archived. NodeRef validatedNodeRef = nodes.validateNode(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE, originalNodeId); if (renditionId != null) { - return renditions.requestContentDirectUrl(validatedNodeRef, null, renditionId, attachment, validFor); + return renditions.requestContentDirectUrl(validatedNodeRef, null, renditionId, attachment, validFor, fileName); } else { - return nodes.requestContentDirectUrl(validatedNodeRef, attachment, validFor); + return nodes.requestContentDirectUrl(validatedNodeRef, attachment, validFor, fileName); } } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java index f255e61b63..dc0087982a 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -3443,9 +3443,9 @@ public class NodesImpl implements Nodes * {@inheritDoc} */ @Override - public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor) + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, boolean attachment, Long validFor, String fileName) { - DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(nodeRef, ContentModel.PROP_CONTENT, attachment, validFor); + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(nodeRef, ContentModel.PROP_CONTENT, attachment, validFor, fileName); if (directAccessUrl == null) { throw new DisabledServiceException("Direct access url isn't available."); diff --git a/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java b/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java index 28f4456d1c..dafb14bc81 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/impl/RenditionsImpl.java @@ -512,7 +512,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware /** * {@inheritDoc} */ - public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor) + public DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, String versionId, String renditionId, boolean attachment, Long validFor, String fileName) { final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionId, null); NodeRef renditionNodeRef = getRenditionByName(validatedNodeRef, renditionId, null); @@ -522,7 +522,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware throw new NotFoundException("The rendition with id: " + renditionId + " was not found."); } - return nodes.requestContentDirectUrl(renditionNodeRef, attachment, validFor); + return nodes.requestContentDirectUrl(renditionNodeRef, attachment, validFor, fileName); } private BinaryResource getContentImpl(NodeRef nodeRef, String renditionId, Parameters parameters) diff --git a/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java index 6dde68adb2..638dbfe9e5 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/model/DirectAccessUrlRequest.java @@ -33,6 +33,7 @@ package org.alfresco.rest.api.model; public class DirectAccessUrlRequest { private Boolean attachment; + private String fileName; public Boolean isAttachment() { @@ -43,4 +44,14 @@ public class DirectAccessUrlRequest { this.attachment = attachment; } + + public String getFileName() + { + return fileName; + } + + public void setFileName(String fileName) + { + this.fileName = fileName; + } } diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java index 56d38291a7..9c9d342dcd 100644 --- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java +++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeRenditionsRelation.java @@ -137,12 +137,13 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Readcontent + * @param attachment {@code true} if an attachment URL is requested, {@code false} for an embedded {@code URL}. + * @param validFor The time at which the direct access {@code URL} will expire. + * @param fileName Optional name for the file when downloaded + * @return A direct access {@code URL} object for the content. + * @throws UnsupportedOperationException if the store is unable to provide the information. + */ + @Auditable(parameters = {"nodeRef", "propertyQName", "validFor"}) + DirectAccessUrl requestContentDirectUrl(NodeRef nodeRef, QName propertyQName, boolean attachment, Long validFor, String fileName); /** * Gets a key-value (String-String) collection of storage headers/properties with their respective values for a specific node reference. diff --git a/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java index 3450885101..57fdb5b420 100644 --- a/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java +++ b/repository/src/test/java/org/alfresco/repo/content/ContentServiceImplUnitTest.java @@ -72,7 +72,8 @@ public class ContentServiceImplUnitTest private static final NodeRef NODE_REF = new NodeRef("content://Node/Ref"); public static final String SOME_CONTENT_URL = "someContentUrl"; - private static final NodeRef NODE_REF_2 = new NodeRef("content://Node/Ref2");; + private static final NodeRef NODE_REF_2 = new NodeRef("content://Node/Ref2"); + public static final String SOME_FILE_NAME = "someFilename"; private static final String X_AMZ_HEADER_1 = "x-amz-header1"; private static final String VALUE_1 = "value1"; @@ -103,7 +104,7 @@ public class ContentServiceImplUnitTest when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_CONTENT)).thenReturn(mockContentData); when(mockContentData.getContentUrl()).thenReturn(SOME_CONTENT_URL); when(mockContentData.getMimetype()).thenReturn("someMimetype"); - when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_NAME)).thenReturn("someFilename"); + when(mockNodeService.getProperty(NODE_REF, ContentModel.PROP_NAME)).thenReturn(SOME_FILE_NAME); } @Test @@ -162,6 +163,40 @@ public class ContentServiceImplUnitTest verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), anyString(), anyString(), anyLong()); } + @Test + public void testRequestContentDirectUrl_StoreRequestContentDirectUrlIsCalledWithFileNameOverride() + { + final String fileNameOverride = "fileNameOverride.txt"; + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, PROP_CONTENT_QNAME, true, 20L, fileNameOverride); + assertNull(directAccessUrl); + verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), eq(fileNameOverride), anyString(), anyLong()); + } + + @Test + public void testRequestContentDirectUrl_StoreRequestContentDirectUrlIsCalledWithNodeNamePropertyWhenFileNameOverrideIsNull() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, PROP_CONTENT_QNAME, true, 20L, null); + assertNull(directAccessUrl); + verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), eq(SOME_FILE_NAME), anyString(), anyLong()); + } + + @Test + public void testRequestContentDirectUrl_StoreRequestContentDirectUrlIsCalledWithNodeNamePropertyWhenFileNameOverrideIsEmpty() + { + setupSystemWideDirectAccessConfig(ENABLED); + when(mockContentStore.isContentDirectUrlEnabled()).thenReturn(ENABLED); + + DirectAccessUrl directAccessUrl = contentService.requestContentDirectUrl(NODE_REF, PROP_CONTENT_QNAME, true, 20L, ""); + assertNull(directAccessUrl); + verify(mockContentStore, times(1)).requestContentDirectUrl(anyString(), eq(true), eq(SOME_FILE_NAME), anyString(), anyLong()); + } + @Test public void shouldReturnStoragePropertiesWhenTheyExist() {