diff --git a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java
index 86a75657ef..b6c011e403 100644
--- a/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java
+++ b/remote-api/src/main/java/org/alfresco/rest/api/nodes/NodeVersionRenditionsRelation.java
@@ -26,18 +26,28 @@
package org.alfresco.rest.api.nodes;
+import javax.servlet.http.HttpServletResponse;
import java.util.List;
+import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException;
+import org.alfresco.rest.api.DirectAccessUrlHelper;
import org.alfresco.rest.api.Renditions;
+import org.alfresco.rest.api.model.DirectAccessUrlRequest;
import org.alfresco.rest.api.model.Rendition;
import org.alfresco.rest.framework.BinaryProperties;
+import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiParam;
+import org.alfresco.rest.framework.core.ResourceParameter;
+import org.alfresco.rest.framework.core.exceptions.DisabledServiceException;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
+import org.alfresco.rest.framework.webscripts.WithResponse;
+import org.alfresco.service.cmr.repository.DirectAccessUrl;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.util.PropertyCheck;
@@ -53,6 +63,7 @@ import org.springframework.extensions.webscripts.Status;
* - GET /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId}
* - DELETE /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId}
* - GET /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId}/content
+ * - POST /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId}/request-direct-access-url
*
* @author janv
*/
@@ -65,12 +76,18 @@ public class NodeVersionRenditionsRelation implements RelationshipResourceAction
InitializingBean
{
private Renditions renditions;
+ private DirectAccessUrlHelper directAccessUrlHelper;
public void setRenditions(Renditions renditions)
{
this.renditions = renditions;
}
+ public void setDirectAccessUrlHelper(DirectAccessUrlHelper directAccessUrlHelper)
+ {
+ this.directAccessUrlHelper = directAccessUrlHelper;
+ }
+
@Override
public void afterPropertiesSet() throws Exception
{
@@ -117,6 +134,36 @@ public class NodeVersionRenditionsRelation implements RelationshipResourceAction
return renditions.getContent(nodeRef, versionId, renditionId, parameters);
}
+ @Operation ("request-direct-access-url")
+ @WebApiParam (name = "directAccessUrlRequest",
+ title = "Request direct access url",
+ description = "Options for direct access url request",
+ kind = ResourceParameter.KIND.HTTP_BODY_OBJECT)
+ @WebApiDescription(title = "Request content url",
+ description="Generates a direct access URL.",
+ successStatus = HttpServletResponse.SC_OK)
+ public DirectAccessUrl requestContentDirectUrl(String nodeId,
+ String versionId,
+ DirectAccessUrlRequest directAccessUrlRequest,
+ Parameters parameters, WithResponse withResponse)
+ {
+ boolean attachment = directAccessUrlHelper.getAttachment(directAccessUrlRequest);
+ Long validFor = directAccessUrlHelper.getDefaultExpiryTimeInSec();
+ NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId);
+ String renditionId = parameters.getRelationship2Id();
+
+ DirectAccessUrl directAccessUrl;
+ try
+ {
+ directAccessUrl = renditions.requestContentDirectUrl(nodeRef, versionId, renditionId, attachment, validFor);
+ }
+ catch (DirectAccessUrlDisabledException ex)
+ {
+ throw new DisabledServiceException(ex.getMessage());
+ }
+ return directAccessUrl;
+ }
+
@WebApiDescription(title = "Delete rendition")
@Override
public void delete(String nodeId, String versionId, Parameters parameters)
diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml
index 73bcb9e08c..11bc9ac49b 100644
--- a/remote-api/src/main/resources/alfresco/public-rest-context.xml
+++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml
@@ -1452,6 +1452,7 @@
+
diff --git a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java
index b22987a9f2..09b07d410b 100644
--- a/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java
+++ b/remote-api/src/test/java/org/alfresco/AppContext02TestSuite.java
@@ -53,6 +53,7 @@ import org.junit.runners.Suite;
org.alfresco.rest.api.tests.NodeApiTest.class,
org.alfresco.rest.api.tests.NodeAssociationsApiTest.class,
org.alfresco.rest.api.tests.NodeVersionsApiTest.class,
+ org.alfresco.rest.api.tests.NodeVersionRenditionsApiTest.class,
org.alfresco.rest.api.tests.QueriesNodesApiTest.class,
org.alfresco.rest.api.tests.QueriesPeopleApiTest.class,
org.alfresco.rest.api.tests.QueriesSitesApiTest.class,
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java
index a3037ca199..b675055ff9 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/AbstractBaseApiTest.java
@@ -25,6 +25,7 @@
*/
package org.alfresco.rest.api.tests;
+import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig;
import org.alfresco.rest.api.tests.client.PublicApiHttpClient;
import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsString;
import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsStringNonNull;
@@ -219,6 +220,11 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return URL_NODES + "/" + nodeId + "/" + REQUEST_DIRECT_ACCESS_URL;
}
+ protected String getRequestVersionRenditionContentDirectUrl(String nodeId, String versionId, String renditionId)
+ {
+ return getNodeVersionRenditionIdUrl(nodeId, versionId, renditionId) + "/" + REQUEST_DIRECT_ACCESS_URL;
+ }
+
protected String getRequestArchivedContentDirectUrl(String nodeId)
{
return URL_DELETED_NODES + "/" + nodeId + "/" + REQUEST_DIRECT_ACCESS_URL;
@@ -724,6 +730,18 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return createNode(parentId, folderName, TYPE_CM_FOLDER, props, Folder.class);
}
+ protected String createUniqueFolder(String parentId) throws Exception
+ {
+ return createFolder(parentId, "folder-" + System.currentTimeMillis()).getId();
+ }
+
+ protected String createUniqueContent(String folderId) throws Exception
+ {
+ Document documentResp = createTextFile(folderId, "file-" + System.currentTimeMillis(),
+ "some text-" + System.currentTimeMillis(), "UTF-8", null);
+ return documentResp.getId();
+ }
+
protected Node createNode(String parentId, String nodeName, String nodeType, Map props) throws Exception
{
return createNode(parentId, nodeName, nodeType, props, Node.class);
@@ -1075,5 +1093,16 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return URL_NODES + "/" + nodeId;
}
+ protected void enableRestDirectAccessUrls()
+ {
+ RestApiDirectUrlConfig dauConfig = (RestApiDirectUrlConfig) applicationContext.getBean("restApiDirectUrlConfig");
+ dauConfig.setEnabled(true);
+ }
+
+ protected void disableRestDirectAccessUrls()
+ {
+ RestApiDirectUrlConfig dauConfig = (RestApiDirectUrlConfig) applicationContext.getBean("restApiDirectUrlConfig");
+ dauConfig.setEnabled(false);
+ }
}
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java
index a01cf9e3b8..4843b7707d 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeVersionRenditionsApiTest.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2020 Alfresco Software Limited
+ * Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -27,20 +27,32 @@ package org.alfresco.rest.api.tests;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.web.scripts.RepositoryContainer;
import org.alfresco.rest.AbstractSingleNetworkSiteTest;
+import org.alfresco.rest.api.DirectAccessUrlHelper;
+import org.alfresco.rest.api.impl.directurl.RestApiDirectUrlConfig;
+import org.alfresco.rest.api.model.Site;
+import org.alfresco.rest.api.nodes.NodesEntityResource;
import org.alfresco.rest.api.tests.client.HttpResponse;
import org.alfresco.rest.api.tests.client.PublicApiClient.Paging;
import org.alfresco.rest.api.tests.client.data.ContentInfo;
import org.alfresco.rest.api.tests.client.data.Document;
import org.alfresco.rest.api.tests.client.data.Node;
import org.alfresco.rest.api.tests.client.data.Rendition;
+import org.alfresco.rest.api.tests.util.MultiPartBuilder;
+import org.alfresco.rest.api.tests.util.MultiPartBuilder.FileData;
+import org.alfresco.rest.api.tests.util.MultiPartBuilder.MultiPartRequest;
import org.alfresco.rest.api.tests.util.RestApiUtil;
import static org.alfresco.rest.api.tests.util.RestApiUtil.toJsonAsString;
import static org.junit.Assert.*;
+import org.alfresco.service.cmr.site.SiteVisibility;
+import org.junit.After;
import org.junit.Test;
+import org.mockito.Mockito;
+import java.io.File;
import java.util.*;
/**
@@ -273,6 +285,138 @@ public class NodeVersionRenditionsApiTest extends AbstractSingleNetworkSiteTest
}
}
+ @Test
+ public void testRequestVersionRenditionContentDirectUrl() throws Exception
+ {
+ setRequestContext(user1);
+
+ RepoService.TestNetwork networkN1;
+ RepoService.TestPerson userOneN1;
+ Site userOneN1Site;
+
+ networkN1 = repoService.createNetworkWithAlias("ping", true);
+ networkN1.create();
+ userOneN1 = networkN1.createUser();
+
+ setRequestContext(networkN1.getId(), userOneN1.getId(), null);
+
+ String siteTitle = "RandomSite" + System.currentTimeMillis();
+ userOneN1Site = createSite(siteTitle, SiteVisibility.PRIVATE);
+
+ String PROP_LTM = "cm:lastThumbnailModification";
+
+ String RENDITION_NAME = "imgpreview";
+
+ String userId = userOneN1.getId();
+ setRequestContext(networkN1.getId(), userOneN1.getId(), null);
+
+ // Create a folder within the site document's library
+ String folderName = "folder" + System.currentTimeMillis();
+ String parentId = getSiteContainerNodeId(userOneN1Site.getId(), "documentLibrary");
+ String folder_Id = createNode(parentId, folderName, TYPE_CM_FOLDER, null).getId();
+
+ // Create multipart request - pdf file
+ String fileName = "quick.pdf";
+ File file = getResourceFile(fileName);
+ MultiPartRequest reqBody = MultiPartBuilder.create()
+ .setFileData(new FileData(fileName, file))
+ .build();
+ Map params = Collections.singletonMap("include", "properties");
+
+ // Upload quick.pdf file into 'folder' - do not include request to create 'doclib' thumbnail
+ HttpResponse response = post(getNodeChildrenUrl(folder_Id), reqBody.getBody(), params, null, "alfresco", reqBody.getContentType(), 201);
+ Document document1 = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ String contentNodeId = document1.getId();
+ assertNotNull(document1.getProperties());
+
+ // pause briefly
+ Thread.sleep(DELAY_IN_MS);
+
+ // Get rendition (not created yet) information for node
+ response = getSingle(getNodeRenditionsUrl(contentNodeId), RENDITION_NAME, 200);
+ Rendition rendition = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Rendition.class);
+ assertNotNull(rendition);
+ assertEquals(Rendition.RenditionStatus.NOT_CREATED, rendition.getStatus());
+
+ params = new HashMap<>();
+ params.put("placeholder", "false");
+ getSingle(getNodeRenditionsUrl(contentNodeId), (RENDITION_NAME + "/content"), params, 404);
+
+ // Create and get 'imgpreview' rendition
+ rendition = createAndGetRendition(contentNodeId, RENDITION_NAME);
+ assertNotNull(rendition);
+
+ params = new HashMap<>();
+ params.put("placeholder", "false");
+ response = getSingle(getNodeRenditionsUrl(contentNodeId), (RENDITION_NAME + "/content"), params, 200);
+
+ byte[] renditionBytes1 = response.getResponseAsBytes();
+ assertNotNull(renditionBytes1);
+
+ // check node details ...
+ params = Collections.singletonMap("include", "properties");
+ response = getSingle(NodesEntityResource.class, contentNodeId, params, 200);
+ Document document1b = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ assertEquals(document1b.getModifiedByUser().getId(), document1.getModifiedByUser().getId());
+
+ // upload another version of "quick.pdf" and check again
+ fileName = "quick-2.pdf";
+ file = getResourceFile(fileName);
+ reqBody = MultiPartBuilder.create()
+ .setFileData(new FileData("quick.pdf", file))
+ .setOverwrite(true)
+ .build();
+
+ response = post(getNodeChildrenUrl(folder_Id), reqBody.getBody(), null, null, "alfresco", reqBody.getContentType(), 201);
+ Document document2 = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ assertEquals(contentNodeId, document2.getId());
+
+ // wait to allow new version of the rendition to be created ...
+ Thread.sleep(DELAY_IN_MS * 4);
+
+ params = new HashMap<>();
+ params.put("placeholder", "false");
+ response = getSingle(getNodeRenditionsUrl(contentNodeId), (RENDITION_NAME + "/content"), params, 200);
+ assertNotNull(response.getResponseAsBytes());
+
+ // check rendition binary has changed
+ assertNotEquals(renditionBytes1, response.getResponseAsBytes());
+
+ params = Collections.singletonMap("include", "properties");
+ response = getSingle(NodesEntityResource.class, contentNodeId, params, 200);
+ Document document2b = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+
+ String contentNodeId2b = document2b.getId();
+ HttpResponse dauResponse = post(getRequestVersionRenditionContentDirectUrl(contentNodeId2b, "1.0", RENDITION_NAME), null, null, null, null, 501);
+ }
+
+ @Test
+ public void testRequestVersionRenditionContentDirectUrlErrorResponses () throws Exception
+ {
+ setRequestContext(user1);
+
+ String folderNodeId = createUniqueFolder(getMyNodeId());
+ String contentNodeId = createUniqueContent(folderNodeId);
+ createVersionRendition(contentNodeId, "1.0", "doclib");
+
+ // REST direct access URLs must be enabled in order to test DAU error responses
+ enableRestDirectAccessUrls();
+
+ // Test error response for node does not exist
+ HttpResponse dauResponseForNoSuchNode = post(getRequestVersionRenditionContentDirectUrl("nosuchnode", "1.0", "doclib"), null, 404);
+
+ // Test error response for node is not a file
+ HttpResponse dauResponseForNodeIsNotAFile = post(getRequestVersionRenditionContentDirectUrl(folderNodeId, "1.0", "doclib"), null, 400);
+
+ // Test error response for version does not exist
+ HttpResponse dauResponseForNoSuchVersion = post(getRequestVersionRenditionContentDirectUrl(contentNodeId, "2.0", "doclib"), null, 404);
+
+ // Test error response for rendition does not exist
+ HttpResponse dauResponseForNoSuchRendition = post(getRequestVersionRenditionContentDirectUrl(contentNodeId, "1.0", "avatar"), null, 404);
+
+ disableRestDirectAccessUrls();
+ }
+
private void checkCreateAndGetVersionRendition(String docId, String versionId, String renditionId) throws Exception
{
String getRenditionsUrl;
@@ -361,6 +505,12 @@ public class NodeVersionRenditionsApiTest extends AbstractSingleNetworkSiteTest
assertTrue(contentInfo.getSizeInBytes() > 0);
}
+ private void createVersionRendition(String contentNodeId, String versionId, String renditionId) throws Exception
+ {
+ getAll(getNodeVersionRenditionsUrl(contentNodeId, versionId), null, 200);
+ checkCreateAndGetVersionRendition(contentNodeId, versionId, renditionId);
+ }
+
@Override
public String getScope()
{