From f80767359fec043ca0d12a94c744deba440c80ee Mon Sep 17 00:00:00 2001
From: Alan Davis
Date: Fri, 14 Jan 2022 10:28:50 +0000
Subject: [PATCH] MNT-22696 Replace Rendition REST API (#880)
Code to reject zero byte renditions
Addition of:
DELETE /nodes/{nodeId}/renditions/{renditionId}
DELETE /nodes/{nodeId}/versions/{versionId}/renditions/{renditionId} end points
Co-authored-by: kmagdziarz
---
.../org/alfresco/rest/api/Renditions.java | 21 ++++-
.../rest/api/impl/RenditionsImpl.java | 27 +++++-
.../api/nodes/NodeRenditionsRelation.java | 11 ++-
.../nodes/NodeVersionRenditionsRelation.java | 21 +++--
.../framework/resource/parameters/Params.java | 4 +-
.../webscripts/ResourceWebScriptDelete.java | 13 ++-
.../rest/api/tests/AbstractBaseApiTest.java | 12 ++-
.../alfresco/rest/api/tests/NodeApiTest.java | 89 ++++++++++++++++++-
.../repo/rendition2/RenditionService2.java | 10 ++-
.../rendition2/RenditionService2Impl.java | 30 ++++++-
.../RenditionService2IntegrationTest.java | 26 +++++-
11 files changed, 243 insertions(+), 21 deletions(-)
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 aa1e352798..ebcece08ef 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
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2021 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
@@ -147,6 +147,25 @@ public interface Renditions
void createRenditions(NodeRef nodeRef, String versionId, List renditions, Parameters parameters)
throws NotFoundException, ConstraintViolatedException;
+ /**
+ * Delete the rendition node.
+ *
+ * @param nodeRef the source nodeRef, ie. live node
+ * @param renditionId the rendition id
+ * @param parameters the {@link Parameters} object to get the parameters passed into the request
+ */
+ void deleteRendition(NodeRef nodeRef, String renditionId, Parameters parameters);
+
+ /**
+ * Delete the rendition node.
+ *
+ * @param nodeRef the source nodeRef, ie. live node
+ * @param versionId the version id (aka version label)
+ * @param renditionId the rendition id
+ * @param parameters the {@link Parameters} object to get the parameters passed into the request
+ */
+ void deleteRendition(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters);
+
/**
* Downloads rendition.
*
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 cd7b665c54..b70dcd8401 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
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2021 Alfresco Software LimitedP
+ * Copyright (C) 2005 - 2022 Alfresco Software LimitedP
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -444,6 +444,31 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
}
+ @Override
+ public void deleteRendition(NodeRef nodeRef, String renditionId, Parameters parameters)
+ {
+ deleteRendition(nodeRef, null, renditionId, parameters);
+ }
+
+ @Override
+ public void deleteRendition(NodeRef nodeRef, String versionId, String renditionId, Parameters parameters)
+ {
+ if (!renditionService2.isEnabled())
+ {
+ throw new DisabledServiceException("Rendition generation has been disabled.");
+ }
+
+ final NodeRef validatedNodeRef = validateNode(nodeRef.getStoreRef(), nodeRef.getId(), versionId, parameters);
+ NodeRef renditionNodeRef = getRenditionByName(validatedNodeRef, renditionId, parameters);
+
+ if (renditionNodeRef == null)
+ {
+ throw new NotFoundException(renditionId + " is not registered.");
+ }
+
+ renditionService2.clearRenditionContentDataInTransaction(renditionNodeRef);
+ }
+
private String getName(Rendition rendition)
{
String renditionName = rendition.getId();
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 54cd41747b..56d38291a7 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
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2021 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
@@ -65,6 +65,7 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read,
RelationshipResourceAction.Create,
RelationshipResourceBinaryAction.Read,
+ RelationshipResourceAction.Delete,
InitializingBean
{
@@ -110,6 +111,14 @@ public class NodeRenditionsRelation implements RelationshipResourceAction.Read,
RelationshipResourceAction.ReadById,
RelationshipResourceAction.Create,
+ RelationshipResourceAction.Delete,
RelationshipResourceBinaryAction.Read,
InitializingBean
{
@@ -115,4 +117,13 @@ public class NodeVersionRenditionsRelation implements RelationshipResourceAction
return renditions.getContent(nodeRef, versionId, renditionId, parameters);
}
+ @WebApiDescription(title = "Delete rendition")
+ @Override
+ public void delete(String nodeId, String versionId, Parameters parameters)
+ {
+ String renditionId = parameters.getRelationship2Id();
+
+ NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId);
+ renditions.deleteRendition(nodeRef, versionId, renditionId, parameters);
+ }
}
diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/resource/parameters/Params.java b/remote-api/src/main/java/org/alfresco/rest/framework/resource/parameters/Params.java
index a3f9fa9613..0026089fe4 100644
--- a/remote-api/src/main/java/org/alfresco/rest/framework/resource/parameters/Params.java
+++ b/remote-api/src/main/java/org/alfresco/rest/framework/resource/parameters/Params.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
@@ -188,6 +188,8 @@ public class Params implements Parameters
builder.append(this.entityId);
builder.append(", relationshipId=");
builder.append(this.relationshipId);
+ builder.append(", relationship2Id=");
+ builder.append(this.relationship2Id);
builder.append(", passedIn=");
builder.append(this.passedIn);
builder.append(", paging=");
diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java
index ea8e62b23a..1541a6e1c5 100644
--- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java
+++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.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
@@ -68,6 +68,7 @@ public class ResourceWebScriptDelete extends AbstractResourceWebScript implement
final Map resourceVars = locator.parseTemplateVars(req.getServiceMatch().getTemplateVars());
final String entityId = resourceVars.get(ResourceLocator.ENTITY_ID);
final String relationshipId = resourceVars.get(ResourceLocator.RELATIONSHIP_ID);
+ final String relationship2Id = resourceVars.get(ResourceLocator.RELATIONSHIP2_ID);
final Params.RecognizedParams params = getRecognizedParams(req);
@@ -78,7 +79,15 @@ public class ResourceWebScriptDelete extends AbstractResourceWebScript implement
return Params.valueOf(params, entityId, relationshipId, req);
case RELATIONSHIP:
// note: relationshipId can be null - when deleting a related set/collection
- return Params.valueOf(params, entityId, relationshipId, req);
+ if (StringUtils.isNotBlank(relationship2Id))
+ {
+ return Params.valueOf(false, entityId, relationshipId, relationship2Id,
+ null, null, null, params, null, req);
+ }
+ else
+ {
+ return Params.valueOf(params, entityId, relationshipId, req);
+ }
case PROPERTY:
final String resourceName = resourceVars.get(ResourceLocator.RELATIONSHIP_RESOURCE);
final String propertyName = resourceVars.get(ResourceLocator.PROPERTY);
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 c6f0d16d77..a3037ca199 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
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2021 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
@@ -1035,6 +1035,16 @@ public abstract class AbstractBaseApiTest extends EnterpriseTestApi
return URL_NODES + "/" + nodeId + "/" + URL_RENDITIONS;
}
+ protected String getNodeRenditionIdUrl(String nodeId, String renditionID)
+ {
+ return URL_NODES + "/" + nodeId + "/" + URL_RENDITIONS + "/" + renditionID;
+ }
+
+ protected String getNodeVersionRenditionIdUrl(String nodeId, String versionId, String renditionID)
+ {
+ return URL_NODES + "/" + nodeId + "/" + URL_VERSIONS + "/" + versionId + "/" + URL_RENDITIONS + "/" + renditionID;
+ }
+
protected String getNodeVersionsUrl(String nodeId)
{
return URL_NODES + "/" + nodeId + "/" + URL_VERSIONS;
diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
index ca61d3e22d..9ceeeb91ac 100644
--- a/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
+++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/NodeApiTest.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2021 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
@@ -6366,5 +6366,92 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
HttpResponse dauResponse = post(getRequestContentDirectUrl(contentNodeId), null, null, null, null, 501);
}
+
+ @Test
+ public void testRequestDeleteRendition() throws Exception
+ {
+ setRequestContext(networkOne.getId(), user1, null);
+
+ String myNodeId = getMyNodeId();
+
+ // Create multipart request - txt file
+ String renditionName = "pdf";
+ String fileName = "quick-1.txt";
+ File file = getResourceFile(fileName);
+ MultiPartRequest reqBody = MultiPartBuilder.create()
+ .setFileData(new FileData(fileName, file))
+ .setRenditions(Collections.singletonList(renditionName))
+ .build();
+
+ //Upload file to user home node
+ HttpResponse response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ String contentNodeId = document.getId();
+
+ // wait and check that rendition is created ...
+ Rendition rendition = waitAndGetRendition(contentNodeId, null, renditionName);
+ assertNotNull(rendition);
+ assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus());
+
+ //clean rendition
+ delete(getNodeRenditionIdUrl(contentNodeId, renditionName), null, null, null, null, 204);
+ //retry to double-check deletion
+ delete(getNodeRenditionIdUrl(contentNodeId, renditionName), null, null, null, null, 404);
+
+ //check if rendition was cleaned
+ HttpResponse getResponse = getSingle(getNodeRenditionIdUrl(contentNodeId, renditionName), null, 200);
+ Rendition renditionDeleted = RestApiUtil.parseRestApiEntry(getResponse.getJsonResponse(), Rendition.class);
+ assertNotNull(renditionDeleted);
+ assertEquals(Rendition.RenditionStatus.NOT_CREATED, renditionDeleted.getStatus());
+ }
+
+ @Test
+ public void testRequestVersionDeleteRendition() throws Exception
+ {
+ setRequestContext(networkOne.getId(), user1, null);
+
+ String myNodeId = getMyNodeId();
+
+ // Create multipart request - txt file
+ String renditionName = "pdf";
+ String fileName = "quick-1.txt";
+ File file = getResourceFile(fileName);
+ MultiPartRequest reqBody = MultiPartBuilder.create()
+ .setFileData(new FileData(fileName, file))
+ .setRenditions(Collections.singletonList(renditionName))
+ .build();
+
+ //Upload file to user home node
+ HttpResponse response = post(getNodeChildrenUrl(myNodeId), reqBody.getBody(), null, reqBody.getContentType(), 201);
+ Document document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
+ String contentNodeId = document.getId();
+
+ //Update file to newer version
+ String content = "The quick brown fox jumps over the lazy dog\n the lazy dog jumps over the quick brown fox";
+ Map params = new HashMap<>();
+ params.put("comment", "my version ");
+
+ document = updateTextFile(contentNodeId, content, params);
+ assertTrue(document.getAspectNames().contains("cm:versionable"));
+ assertNotNull(document.getProperties());
+ assertEquals("1.1", document.getProperties().get("cm:versionLabel"));
+
+ // create rendition for old version and check that rendition is created ...
+ Rendition renditionUpdated = createAndGetRendition(contentNodeId, "1.0", renditionName);
+ assertNotNull(renditionUpdated);
+ assertEquals(Rendition.RenditionStatus.CREATED, renditionUpdated.getStatus());
+
+ //clean rendition
+ delete(getNodeVersionRenditionIdUrl(contentNodeId, "1.0", renditionName), null, null, null, null, 204);
+ //retry to double-check deletion
+ delete(getNodeVersionRenditionIdUrl(contentNodeId, "1.0", renditionName), null, null, null, null, 404);
+
+ //check if rendition was cleaned
+ HttpResponse getResponse = getSingle(getNodeVersionRenditionIdUrl(contentNodeId, "1.0", renditionName), null, 200);
+ Rendition renditionDeleted = RestApiUtil.parseRestApiEntry(getResponse.getJsonResponse(), Rendition.class);
+ assertNotNull(renditionDeleted);
+ assertEquals(Rendition.RenditionStatus.NOT_CREATED, renditionDeleted.getStatus());
+ }
+
}
diff --git a/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2.java b/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2.java
index 6e5b219be2..bd7d4c3b7f 100644
--- a/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2.java
+++ b/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2018 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
@@ -112,6 +112,14 @@ public interface RenditionService2
@NotAuditable
ChildAssociationRef getRenditionByName(NodeRef sourceNodeRef, String renditionName);
+ /**
+ * This method clears source nodeRef rendition content and content hash code using supplied rendition name.
+ *
+ * @param renditionNode the rendition node
+ */
+ @NotAuditable
+ void clearRenditionContentDataInTransaction(NodeRef renditionNode);
+
/**
* Indicates if renditions are enabled. Set using the {@code system.thumbnail.generate} value.
*/
diff --git a/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2Impl.java b/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2Impl.java
index 1f540b6af7..cbe5e95f72 100644
--- a/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2Impl.java
+++ b/repository/src/main/java/org/alfresco/repo/rendition2/RenditionService2Impl.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2021 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
@@ -38,6 +38,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.util.PostTxnCallbackScheduler;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -553,11 +554,21 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
contentWriter.setEncoding(DEFAULT_ENCODING);
ContentWriter renditionWriter = contentWriter;
renditionWriter.putContent(transformInputStream);
- if (logger.isDebugEnabled())
+
+ ContentReader contentReader = renditionWriter.getReader();
+ long sizeOfRendition = contentReader.getSize();
+ if (sizeOfRendition > 0L)
{
- logger.debug("Set rendition hashcode for " + renditionName);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Set rendition hashcode for " + renditionName);
+ }
+ nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
+ }
+ else
+ {
+ logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
+ clearRenditionContentData(renditionNode);
}
- nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
}
catch (Exception e)
{
@@ -892,6 +903,17 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
}
}
+ @Override
+ public void clearRenditionContentDataInTransaction(NodeRef renditionNode)
+ {
+ AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () ->
+ transactionService.getRetryingTransactionHelper().doInTransaction(() ->
+ {
+ clearRenditionContentData(renditionNode);
+ return null;
+ }, false, true));
+ }
+
@Override
public boolean isEnabled()
{
diff --git a/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java b/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java
index 4cb250e266..f32e425847 100644
--- a/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java
+++ b/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2021 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
@@ -28,6 +28,8 @@ package org.alfresco.repo.rendition2;
import static org.alfresco.model.ContentModel.PROP_CONTENT;
import static org.junit.Assert.assertNotEquals;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
import java.util.List;
import org.alfresco.model.ContentModel;
@@ -358,7 +360,25 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
*
*/
@Test
- public void testRenditionWithoutContentButWithContentHashCode()
+ public void testRenditionWithNullContentButWithContentHashCode()
+ {
+ testRenditionWithoutContentButWithContentHashCode(null);
+ }
+
+ /**
+ * Tests if a rendition without content (but with contentHashCode) can be generated again.
+ *
+ * If the rendition consumption receives a zero length InputStream, the contentHashCode should be cleaned from the
+ * rendition node, allowing new requests to generate the rendition.
+ *
+ */
+ @Test
+ public void testRenditionWithZeroContentButWithContentHashCode()
+ {
+ testRenditionWithoutContentButWithContentHashCode(new ByteArrayInputStream(new byte[0]));
+ }
+
+ private void testRenditionWithoutContentButWithContentHashCode(InputStream transformInputStream)
{
// Create a node with an actual rendition
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
@@ -379,7 +399,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
// This is explicitly called to prove that rendition hash code will be cleaned (as opposite to previous behavior)
RenditionDefinition2 renditionDefinition = renditionDefinitionRegistry2.getRenditionDefinition(DOC_LIB);
AuthenticationUtil.runAs(() -> {
- renditionService2.consume(sourceNodeRef, null, renditionDefinition, contentHashCode);
+ renditionService2.consume(sourceNodeRef, transformInputStream, renditionDefinition, contentHashCode);
return null;
}, ADMIN);
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false);