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 4de95b295c..ceac773ede 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 - 2022 Alfresco Software Limited + * Copyright (C) 2005 - 2025 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,6 +25,24 @@ */ package org.alfresco.repo.rendition2; +import static org.alfresco.model.ContentModel.PROP_CONTENT; +import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE; +import static org.alfresco.service.namespace.QName.createQName; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + import org.alfresco.model.ContentModel; import org.alfresco.model.RenditionModel; import org.alfresco.repo.content.ContentServicePolicies; @@ -51,23 +69,6 @@ import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyCheck; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; - -import java.io.InputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.alfresco.model.ContentModel.PROP_CONTENT; -import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE; -import static org.alfresco.service.namespace.QName.createQName; /** * The Async Rendition service. Replaces the original deprecated RenditionService. @@ -80,11 +81,19 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea public static final QName DEFAULT_RENDITION_CONTENT_PROP = ContentModel.PROP_CONTENT; public static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN; + public static final String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract"; + public static final String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed"; public static final String DEFAULT_ENCODING = "UTF-8"; public static final int SOURCE_HAS_NO_CONTENT = -1; public static final int RENDITION2_DOES_NOT_EXIST = -2; + // Allowed mimetypes to support text or metadata extract transforms when thumbnails are disabled. + private static final Set ALLOWED_MIMETYPES = Set.of( + MimetypeMap.MIMETYPE_TEXT_PLAIN, + MIMETYPE_METADATA_EXTRACT, + MIMETYPE_METADATA_EMBED); + private static Log logger = LogFactory.getLog(RenditionService2Impl.class); // As Async transforms and renditions are so similar, this class provides a way to provide the code that is different. @@ -95,12 +104,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea abstract RenditionDefinition2 getRenditionDefinition(); void handleUnsupported(UnsupportedOperationException e) - { - } + {} void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode) - { - } + {} } private TransactionService transactionService; @@ -217,8 +224,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea @Override public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition) { - requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() - { + requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() { @Override public String getName() { @@ -237,8 +243,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea @Override public void render(NodeRef sourceNodeRef, String renditionName) { - requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() - { + requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() { @Override public String getName() { @@ -261,7 +266,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea @Override public void handleUnsupported(UnsupportedOperationException e) { - // On the initial request for a rendition throw the exception. + // On the initial request for a rendition throw the exception. NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName); if (renditionNode == null) { @@ -277,7 +282,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea int renditionContentHashCode = getRenditionContentHashCode(renditionNode); if (logger.isDebugEnabled()) { - logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode+ " hashCodes"); + logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode + " hashCodes"); } if (renditionContentHashCode == sourceContentHashCode) { @@ -291,7 +296,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea { try { - if (!isEnabled()) + if (!isAsyncAllowed(renderOrTransform)) { throw new RenditionService2Exception("Async transforms and renditions are disabled " + "(system.thumbnail.generate=false or renditionService2.enabled=false)."); @@ -299,14 +304,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea if (!nodeService.exists(sourceNodeRef)) { - throw new IllegalArgumentException(renderOrTransform.getName()+ ": The supplied sourceNodeRef "+sourceNodeRef+" does not exist."); + throw new IllegalArgumentException(renderOrTransform.getName() + ": The supplied sourceNodeRef " + sourceNodeRef + " does not exist."); } RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition(); if (logger.isDebugEnabled()) { - logger.debug(renderOrTransform.getName()+ ": transform " +sourceNodeRef); + logger.debug(renderOrTransform.getName() + ": transform " + sourceNodeRef); } AtomicBoolean supported = new AtomicBoolean(true); @@ -328,14 +333,13 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } String user = AuthenticationUtil.getRunAsUser(); - RetryingTransactionHelper.RetryingTransactionCallback callback = () -> - { + RetryingTransactionHelper.RetryingTransactionCallback callback = () -> { int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); if (!supported.get()) { if (logger.isDebugEnabled()) { - logger.debug(renderOrTransform.getName() +" is not supported. " + + logger.debug(renderOrTransform.getName() + " is not supported. " + "The content might be too big or the source mimetype cannot be converted."); } failure(sourceNodeRef, renditionDefinition, sourceContentHashCode); @@ -372,26 +376,24 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode) { // The original transaction may have already have failed - AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode); - return null; - }, false, true)); + AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode); + return null; + }, false, true)); } public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition, - int transformContentHashCode) + int transformContentHashCode) { int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); if (logger.isDebugEnabled()) { - logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode+" hashcodes"); + logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode + " hashcodes"); } if (renditionDefinition instanceof TransformDefinition) { - TransformDefinition transformDefinition = (TransformDefinition)renditionDefinition; + TransformDefinition transformDefinition = (TransformDefinition) renditionDefinition; String targetMimetype = transformDefinition.getTargetMimetype(); if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype)) { @@ -413,7 +415,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, - TransformDefinition transformDefinition, int transformContentHashCode) + TransformDefinition transformDefinition, int transformContentHashCode) { if (transformInputStream == null) { @@ -440,7 +442,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, - TransformDefinition transformDefinition, int transformContentHashCode) + TransformDefinition transformDefinition, int transformContentHashCode) { if (transformInputStream == null) { @@ -468,7 +470,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream, - TransformDefinition transformDefinition, int transformContentHashCode) + TransformDefinition transformDefinition, int transformContentHashCode) { if (logger.isDebugEnabled()) { @@ -484,12 +486,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } /** - * Takes a transformation (InputStream) and attaches it as a rendition to the source node. - * Does nothing if there is already a newer rendition. - * If the transformInputStream is null, this is taken to be a transform failure. + * Takes a transformation (InputStream) and attaches it as a rendition to the source node. Does nothing if there is already a newer rendition. If the transformInputStream is null, this is taken to be a transform failure. */ private void consumeRendition(NodeRef sourceNodeRef, int sourceContentHashCode, InputStream transformInputStream, - RenditionDefinition2 renditionDefinition, int transformContentHashCode) + RenditionDefinition2 renditionDefinition, int transformContentHashCode) { String renditionName = renditionDefinition.getRenditionName(); if (transformContentHashCode != sourceContentHashCode) @@ -507,93 +507,92 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea (transformInputStream == null ? " to null as the transform failed" : " to the transform result")); } - AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> + AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + // Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node + NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName); + boolean createRenditionNode = renditionNode == null; + boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED); + try + { + ruleService.disableRuleType(RuleType.UPDATE); + behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); + behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); + + // If they do not exist create the rendition association and the rendition node. + if (createRenditionNode) + { + renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition); + } + else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2)) + { + nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null); + if (logger.isDebugEnabled()) + { + logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef); + } + } + if (logger.isDebugEnabled()) + { + logger.debug("Set ThumbnailLastModified for " + renditionName); + } + setThumbnailLastModified(sourceNodeRef, renditionName); + + if (transformInputStream != null) { - // Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node - NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName); - boolean createRenditionNode = renditionNode == null; - boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED); try { - ruleService.disableRuleType(RuleType.UPDATE); - behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); - behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); + // Set or replace rendition content + ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true); + String targetMimetype = renditionDefinition.getTargetMimetype(); + contentWriter.setMimetype(targetMimetype); + contentWriter.setEncoding(DEFAULT_ENCODING); + ContentWriter renditionWriter = contentWriter; + renditionWriter.putContent(transformInputStream); - // If they do not exist create the rendition association and the rendition node. - if (createRenditionNode) + ContentReader contentReader = renditionWriter.getReader(); + long sizeOfRendition = contentReader.getSize(); + if (sizeOfRendition > 0L) { - renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition); - } - else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2)) - { - nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null); if (logger.isDebugEnabled()) { - logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef); - } - } - if (logger.isDebugEnabled()) - { - logger.debug("Set ThumbnailLastModified for " + renditionName); - } - setThumbnailLastModified(sourceNodeRef, renditionName); - - if (transformInputStream != null) - { - try - { - // Set or replace rendition content - ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true); - String targetMimetype = renditionDefinition.getTargetMimetype(); - contentWriter.setMimetype(targetMimetype); - contentWriter.setEncoding(DEFAULT_ENCODING); - ContentWriter renditionWriter = contentWriter; - renditionWriter.putContent(transformInputStream); - - ContentReader contentReader = renditionWriter.getReader(); - long sizeOfRendition = contentReader.getSize(); - if (sizeOfRendition > 0L) - { - 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); - } - } - catch (Exception e) - { - logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef); - throw e; + 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); } - - if (!sourceHasAspectRenditioned) - { - nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null); - } } catch (Exception e) { - throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e); + logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef); + throw e; } - finally - { - behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); - behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); - ruleService.enableRuleType(RuleType.UPDATE); - } - return null; - }, false, true)); + } + else + { + clearRenditionContentData(renditionNode); + } + + if (!sourceHasAspectRenditioned) + { + nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null); + } + } + catch (Exception e) + { + throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e); + } + finally + { + behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); + behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); + ruleService.enableRuleType(RuleType.UPDATE); + } + return null; + }, false, true)); } } @@ -634,14 +633,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea if (logger.isTraceEnabled()) { - logger.trace("Setting thumbnail last modified date to " + lastModifiedValue +" on source node: " + sourceNodeRef); + logger.trace("Setting thumbnail last modified date to " + lastModifiedValue + " on source node: " + sourceNodeRef); } if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_THUMBNAIL_MODIFICATION)) { List thumbnailMods = (List) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA); String target = null; - for (String currThumbnailMod: thumbnailMods) + for (String currThumbnailMod : thumbnailMods) { if (currThumbnailMod.startsWith(prefix)) { @@ -665,8 +664,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } /** - * Returns the hash code of the source node's content url. As transformations may be returned in a different - * sequences to which they were requested, this is used work out if a rendition should be replaced. + * Returns the hash code of the source node's content url. As transformations may be returned in a different sequences to which they were requested, this is used work out if a rendition should be replaced. */ private int getSourceContentHashCode(NodeRef sourceNodeRef) { @@ -675,7 +673,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea if (contentData != null) { // Originally we used the contentData URL, but that is not enough if the mimetype changes. - String contentString = contentData.getContentUrl()+contentData.getMimetype(); + String contentString = contentData.getContentUrl() + contentData.getMimetype(); if (contentString != null) { hashCode = contentString.hashCode(); @@ -685,13 +683,11 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } /** - * Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist. - * Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was - * not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed. + * Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist. Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed. */ private int getRenditionContentHashCode(NodeRef renditionNode) { - if ( renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2)) + if (renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2)) { return RENDITION2_DOES_NOT_EXIST; } @@ -699,7 +695,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE); return hashCode == null ? SOURCE_HAS_NO_CONTENT - : (int)hashCode; + : (int) hashCode; } private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName) @@ -773,11 +769,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } /** - * This method checks whether the specified source node is of a content class which has been registered for - * rendition prevention. + * This method checks whether the specified source node is of a content class which has been registered for rendition prevention. * - * @param sourceNode the node to check. - * @throws RenditionService2PreventedException if the source node is configured for rendition prevention. + * @param sourceNode + * the node to check. + * @throws RenditionService2PreventedException + * if the source node is configured for rendition prevention. */ // This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...) private void checkSourceNodeForPreventionClass(NodeRef sourceNode) @@ -823,7 +820,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea for (ChildAssociationRef childAssoc : childAsocs) { - NodeRef renditionNode = childAssoc.getChildRef(); + NodeRef renditionNode = childAssoc.getChildRef(); if (isRenditionAvailable(sourceNodeRef, renditionNode)) { result.add(childAssoc); @@ -833,8 +830,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } /** - * Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl - * and out of date renditions or those still being created don't have a matching contentHashCode. + * Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl and out of date renditions or those still being created don't have a matching contentHashCode. */ public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode) { @@ -852,7 +848,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea int renditionContentHashCode = getRenditionContentHashCode(renditionNode); if (logger.isDebugEnabled()) { - logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode+" hashcodes"); + logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode + " hashcodes"); } if (sourceContentHashCode != renditionContentHashCode) { @@ -892,19 +888,17 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } ChildAssociationRef childAssoc = renditions.get(0); NodeRef renditionNode = childAssoc.getChildRef(); - return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null: childAssoc; + return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null : childAssoc; } } @Override public void clearRenditionContentDataInTransaction(NodeRef renditionNode) { - AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - clearRenditionContentData(renditionNode); - return null; - }, false, true)); + AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + clearRenditionContentData(renditionNode); + return null; + }, false, true)); } @Override @@ -950,4 +944,23 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } } + // Checks if the given transform callback is a text extract transform for content indexing or metadata extract/embed. + private boolean isTextOrMetadataExtractTransform(RenderOrTransformCallBack renderOrTransform) + { + RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition(); + return renditionDefinition != null && ALLOWED_MIMETYPES.contains(renditionDefinition.getTargetMimetype()); + } + + private boolean isAsyncAllowed(RenderOrTransformCallBack renderOrTransform) + { + // If enabled is false, all async transforms/renditions must be blocked + if (!enabled) + { + return false; + } + + // If thumbnails are disabled, allow only text extract or metadata extract/embed transforms + return thumbnailsEnabled || isTextOrMetadataExtractTransform(renderOrTransform); + } + } 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 ec72dcfbb5..1b6e4e87c2 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 - 2022 Alfresco Software Limited + * Copyright (C) 2005 - 2025 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of @@ -25,16 +25,21 @@ */ package org.alfresco.repo.rendition2; -import static org.alfresco.model.ContentModel.PROP_CONTENT; import static org.junit.Assert.assertNotEquals; +import static org.alfresco.model.ContentModel.PROP_CONTENT; + import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import org.junit.BeforeClass; +import org.junit.Test; + import org.alfresco.model.ContentModel; import org.alfresco.model.RenditionModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -44,8 +49,6 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.junit.BeforeClass; -import org.junit.Test; /** * Integration tests for {@link RenditionService2} @@ -62,37 +65,37 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati // PDF transformation @Test - public void testLocalRenderPdfToJpegMedium() + public void testLocalRenderPdfToJpegMedium() { checkRendition("quick.pdf", "medium", true); } @Test - public void testLocalRenderPdfToDoclib() + public void testLocalRenderPdfToDoclib() { checkRendition("quick.pdf", "doclib", true); } @Test - public void testLocalRenderPdfJpegImgpreview() + public void testLocalRenderPdfJpegImgpreview() { checkRendition("quick.pdf", "imgpreview", true); } @Test - public void testLocalRenderPdfPngAvatar() + public void testLocalRenderPdfPngAvatar() { checkRendition("quick.pdf", "avatar", true); } @Test - public void testLocalRenderPdfPngAvatar32() + public void testLocalRenderPdfPngAvatar32() { checkRendition("quick.pdf", "avatar32", true); } @Test - public void testLocalRenderPdfFlashWebpreview() + public void testLocalRenderPdfFlashWebpreview() { checkRendition("quick.pdf", "webpreview", false); } @@ -100,43 +103,43 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati // DOCX transformation @Test - public void testLocalRenderDocxJpegMedium() + public void testLocalRenderDocxJpegMedium() { checkRendition("quick.docx", "medium", true); } @Test - public void testLocalRenderDocxDoclib() + public void testLocalRenderDocxDoclib() { checkRendition("quick.docx", "doclib", true); } @Test - public void testLocalRenderDocxJpegImgpreview() + public void testLocalRenderDocxJpegImgpreview() { checkRendition("quick.docx", "imgpreview", true); } @Test - public void testLocalRenderDocxPngAvatar() + public void testLocalRenderDocxPngAvatar() { checkRendition("quick.docx", "avatar", true); } @Test - public void testLocalRenderDocxPngAvatar32() + public void testLocalRenderDocxPngAvatar32() { checkRendition("quick.docx", "avatar32", true); } @Test - public void testLocalRenderDocxFlashWebpreview() + public void testLocalRenderDocxFlashWebpreview() { checkRendition("quick.docx", "webpreview", false); } @Test - public void testLocalRenderDocxPdf() + public void testLocalRenderDocxPdf() { checkRendition("quick.docx", "pdf", true); } @@ -150,7 +153,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void changedSourceToNullContent() + public void changedSourceToNullContent() { NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); render(ADMIN, sourceNodeRef, DOC_LIB); @@ -158,8 +161,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati clearContent(ADMIN, sourceNodeRef); render(ADMIN, sourceNodeRef, DOC_LIB); - ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> - renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN); + ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); assertNull("There should be no rendition as there was no content", assoc); } @@ -190,8 +192,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati clearContent(ADMIN, sourceNodeRef); render(ADMIN, sourceNodeRef, DOC_LIB); - ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> - renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN); + ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); assertNull("There should be no rendition as there was no content", assoc); @@ -201,7 +202,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void testCreateRenditionByUser() + public void testCreateRenditionByUser() { String userName = createRandomUser(); NodeRef sourceNodeRef = createSource(userName, "quick.jpg"); @@ -211,13 +212,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void testReadRenditionByOtherUser() + public void testReadRenditionByOtherUser() { String ownerUserName = createRandomUser(); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); String otherUserName = createRandomUser(); - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); return null; }); @@ -231,13 +231,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void testRenderByReader() + public void testRenderByReader() { String ownerUserName = createRandomUser(); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); String otherUserName = createRandomUser(); - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); return null; }); @@ -251,14 +250,13 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void testAccessWithNoPermissions() + public void testAccessWithNoPermissions() { String ownerUserName = createRandomUser(); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); render(ownerUserName, sourceNodeRef, DOC_LIB); String noPermissionsUser = createRandomUser(); - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { permissionService.setPermission(sourceNodeRef, noPermissionsUser, PermissionService.ALL_PERMISSIONS, false); return null; }); @@ -280,12 +278,9 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName)); - NodeRef oldRendition = AuthenticationUtil.runAs(() -> - renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName); + NodeRef oldRendition = AuthenticationUtil.runAs(() -> renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName); assertFalse("The rendition should be generated by old Rendition Service", AuthenticationUtil.runAs(() -> nodeService.hasAspect(oldRendition, RenditionModel.ASPECT_RENDITION2), ownerUserName)); @@ -335,12 +330,10 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati renditionService2.setEnabled(false); // Call 'clearRenditionContentData' method directly to prove rendition content will be cleaned - AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB); - return null; - }), ADMIN); + AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB); + return null; + }), ADMIN); // The rendition should not have content by now assertNull("Rendition has content", nodeService.getProperty(renditionNodeRef, ContentModel.PROP_CONTENT)); @@ -356,8 +349,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati /** * Tests if a rendition without content (but with contentHashCode) can be generated again. *

- * If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the - * rendition node, allowing new requests to generate the rendition. + * If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the rendition node, allowing new requests to generate the rendition. *

*/ @Test @@ -369,8 +361,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati /** * 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. + * 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 @@ -492,22 +483,16 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); assertNotNull("The old renditions service did not render", waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true)); List lastThumbnailModification = transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - (List) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN)); updateContent(ADMIN, sourceNodeRef, "quick.png"); List newThumbnailModification = null; for (int i = 0; i < 5; i++) { newThumbnailModification = transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - (List) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN)); if (!newThumbnailModification.equals(lastThumbnailModification)) { break; @@ -579,9 +564,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); renditionService2.setEnabled(true); @@ -652,9 +635,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); transactionService.getRetryingTransactionHelper() - .doInTransaction(() -> - AuthenticationUtil.runAs(() -> - renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); + .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN)); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); renditionService2.setEnabled(true); @@ -682,4 +663,57 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati renditionService2.setEnabled(true); } } + + @Test + public void testTextExtractTransformAllowedWhenThumbnailDisabled() + { + // create a source node + NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf"); + assertNotNull("Node not generated", sourceNodeRef); + String replyQueue = "org.test.queue"; + String targetMimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN; + + TransformDefinition textExtractTransform = new TransformDefinition( + targetMimetype, + java.util.Collections.emptyMap(), + "clientData", + replyQueue, + "requestId"); + + renditionService2.setThumbnailsEnabled(false); + try + { + // Should NOT throw, as this is a text extract transform + AuthenticationUtil.runAs(() -> { + transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + renditionService2.transform(sourceNodeRef, textExtractTransform); + return null; + }); + return null; + }, ADMIN); + } + finally + { + renditionService2.setThumbnailsEnabled(true); + } + } + + @Test + public void testMetadataExtractTransformAllowedWhenThumbnailDisabled() + { + // create a source node + NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf"); + assertNotNull("Node not generated", sourceNodeRef); + renditionService2.setThumbnailsEnabled(false); + try + { + // Should NOT throw, as this is a metadata extract transform + extract(ADMIN, sourceNodeRef); + waitForExtract(ADMIN, sourceNodeRef, true); + } + finally + { + renditionService2.setThumbnailsEnabled(true); + } + } }