From 798a3faa5b5fdfda50b47bb9b6859b43f7195112 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 11 Feb 2022 11:35:28 +0000 Subject: [PATCH] ACS-1988 Implement DAU Version Renditions code (#931) * ACS-1988 Implement DAU Version Renditions code * ACS-1988 Replace directAccessUrlRequest with request-direct-access-url * ACS-1988 fix and move DAU version rendition test * ACS-1988 Add NodeVersionRenditionsApiTest to test suite * ACS-1988 Remove unused import * ACS-1988 add error response tests * ACS-1988 remove redundant constant * ACS-1988 updates from review * ACS-1988 updates from review --- .../nodes/NodeVersionRenditionsRelation.java | 47 ++++++ .../alfresco/public-rest-context.xml | 1 + .../org/alfresco/AppContext02TestSuite.java | 1 + .../rest/api/tests/AbstractBaseApiTest.java | 29 ++++ .../tests/NodeVersionRenditionsApiTest.java | 152 +++++++++++++++++- 5 files changed, 229 insertions(+), 1 deletion(-) 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() {