diff --git a/.secrets.baseline b/.secrets.baseline index cd556fcebe..e532c3ec80 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -1539,7 +1539,7 @@ "filename": "repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 127, + "line_number": 130, "is_secret": false } ], @@ -1888,5 +1888,5 @@ } ] }, - "generated_at": "2025-01-10T19:40:38Z" + "generated_at": "2025-03-31T12:42:09Z" } 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 bd7d4c3b7f..0d71273188 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 - 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,35 +25,27 @@ */ package org.alfresco.repo.rendition2; +import java.util.List; + import org.alfresco.service.NotAuditable; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; -import java.util.List; - /** - * The Async Rendition service. Replaces the original rendition services which included synchronous renditions and - * asynchronous methods with Java call backs.

+ * The Async Rendition service. Replaces the original rendition services which included synchronous renditions and asynchronous methods with Java call backs. + *

* - * Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated - * {@link RenditionDefinitionRegistry2}.

+ * Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated {@link RenditionDefinitionRegistry2}. + *

* * Unlike the original RenditionService this service, it: *

* * @author adavis @@ -66,29 +58,30 @@ public interface RenditionService2 RenditionDefinitionRegistry2 getRenditionDefinitionRegistry2(); /** - * This method asynchronously transforms content to a target mimetype with transform options supplied in the - * {@code transformDefinition}. A response is set on a message queue once the transform is complete or fails, - * together with some client supplied data. The response queue and client data are also included in the - * transformDefinition.

+ * This method asynchronously transforms content to a target mimetype with transform options supplied in the {@code transformDefinition}. A response is set on a message queue once the transform is complete or fails, together with some client supplied data. The response queue and client data are also included in the transformDefinition. + *

* - * This method does not create a rendition node, but uses the same code as renditions to perform the transform. The - * {@code transformDefinition} extends {@link RenditionDefinition2}, but is not stored in a - * {@link RenditionDefinitionRegistry2}, as it is transient in nature. + * This method does not create a rendition node, but uses the same code as renditions to perform the transform. The {@code transformDefinition} extends {@link RenditionDefinition2}, but is not stored in a {@link RenditionDefinitionRegistry2}, as it is transient in nature. * - * @param sourceNodeRef the node from which the content is retrieved. - * @param transformDefinition which defines the transform, where to sent the response and some client specified data. - * @throws UnsupportedOperationException if the transform is not supported. + * @param sourceNodeRef + * the node from which the content is retrieved. + * @param transformDefinition + * which defines the transform, where to sent the response and some client specified data. + * @throws UnsupportedOperationException + * if the transform is not supported. */ @NotAuditable public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition); /** - * This method asynchronously renders content as specified by the {@code renditionName}. The content to be - * rendered is provided by {@code sourceNodeRef}. + * This method asynchronously renders content as specified by the {@code renditionName}. The content to be rendered is provided by {@code sourceNodeRef}. * - * @param sourceNodeRef the node from which the content is retrieved. - * @param renditionName the rendition to be performed. - * @throws UnsupportedOperationException if the transform is not supported AND the rendition has not been created before. + * @param sourceNodeRef + * the node from which the content is retrieved. + * @param renditionName + * the rendition to be performed. + * @throws UnsupportedOperationException + * if the transform is not supported AND the rendition has not been created before. */ @NotAuditable public void render(NodeRef sourceNodeRef, String renditionName); @@ -104,10 +97,11 @@ public interface RenditionService2 /** * This method gets the rendition of the {@code sourceNodeRef} identified by its name. * - * @param sourceNodeRef the source node for the renditions - * @param renditionName the renditionName used to identify a rendition. - * @return the {@link ChildAssociationRef} which links the source node to the - * rendition or null if there is no rendition or it is not up to date. + * @param sourceNodeRef + * the source node for the renditions + * @param renditionName + * the renditionName used to identify a rendition. + * @return the {@link ChildAssociationRef} which links the source node to the rendition or null if there is no rendition or it is not up to date. */ @NotAuditable ChildAssociationRef getRenditionByName(NodeRef sourceNodeRef, String renditionName); @@ -115,7 +109,8 @@ public interface RenditionService2 /** * This method clears source nodeRef rendition content and content hash code using supplied rendition name. * - * @param renditionNode the rendition node + * @param renditionNode + * the rendition node */ @NotAuditable void clearRenditionContentDataInTransaction(NodeRef renditionNode); @@ -124,4 +119,13 @@ public interface RenditionService2 * Indicates if renditions are enabled. Set using the {@code system.thumbnail.generate} value. */ boolean isEnabled(); -} \ No newline at end of file + + /** + * This method forces the content hash code for every {@code sourceNodeRef} renditions. + * + * @param sourceNodeRef + * the source node to update renditions hash code + */ + @NotAuditable + void forceRenditionsContentHashCode(NodeRef sourceNodeRef); +} 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..730b476b03 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. @@ -95,12 +96,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea abstract RenditionDefinition2 getRenditionDefinition(); void handleUnsupported(UnsupportedOperationException e) - { - } + {} void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode) - { - } + {} } private TransactionService transactionService; @@ -217,8 +216,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 +235,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 +258,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 +274,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) { @@ -299,14 +296,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 +325,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 +368,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 +407,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 +434,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 +462,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 +478,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 +499,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 +625,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 +656,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 +665,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 +675,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 +687,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 +761,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 +812,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 +822,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 +840,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 +880,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 +936,35 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea } } + @Override + public void forceRenditionsContentHashCode(NodeRef sourceNodeRef) + { + if (sourceNodeRef != null && nodeService.exists(sourceNodeRef)) + { + List renditions = getRenditionChildAssociations(sourceNodeRef); + if (renditions != null) + { + int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); + for (ChildAssociationRef rendition : renditions) + { + NodeRef renditionNode = rendition.getChildRef(); + if (nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2)) + { + int renditionContentHashCode = getRenditionContentHashCode(renditionNode); + String renditionName = rendition.getQName().getLocalName(); + if (sourceContentHashCode != renditionContentHashCode) + { + if (logger.isDebugEnabled()) + { + logger.debug("Update content hash code for rendition " + renditionName + " of node " + + sourceNodeRef); + } + nodeService.setProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE, + sourceContentHashCode); + } + } + } + } + } + } } diff --git a/repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java b/repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java index 2b8826659c..65e1cec8ea 100644 --- a/repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java +++ b/repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.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 @@ -26,6 +26,7 @@ package org.alfresco.repo.rendition2; import static java.lang.Thread.sleep; + import static org.alfresco.model.ContentModel.PROP_CONTENT; import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE; import static org.alfresco.repo.content.MimetypeMap.EXTENSION_BINARY; @@ -35,6 +36,15 @@ import java.io.FileNotFoundException; import java.io.Serializable; import java.util.Collections; +import junit.framework.AssertionFailedError; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.quartz.CronExpression; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.ResourceUtils; + import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.metadata.AsynchronousExtractor; @@ -45,6 +55,7 @@ import org.alfresco.repo.thumbnail.ThumbnailRegistry; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.rendition.RenditionService; 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; @@ -52,6 +63,7 @@ import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; @@ -61,15 +73,6 @@ import org.alfresco.transform.registry.TransformServiceRegistry; import org.alfresco.util.BaseSpringTest; import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.quartz.CronExpression; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.util.ResourceUtils; - -import junit.framework.AssertionFailedError; /** * Class unites common utility methods for {@link org.alfresco.repo.rendition2} package tests. @@ -128,6 +131,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest protected static final String ADMIN = "admin"; protected static final String DOC_LIB = "doclib"; + protected static final String PDF = "pdf"; private CronExpression origLocalTransCron; private CronExpression origRenditionCron; @@ -152,7 +156,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest // Strict MimetypeCheck System.setProperty("transformer.strict.mimetype.check", "true"); - // Retry on DifferentMimetype + // Retry on DifferentMimetype System.setProperty("content.transformer.retryOn.different.mimetype", "true"); } @@ -181,7 +185,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest if (transformServiceRegistry instanceof LocalTransformServiceRegistry) { - ((LocalTransformServiceRegistry)transformServiceRegistry).setEnabled(localTransformServiceEnabled); + ((LocalTransformServiceRegistry) transformServiceRegistry).setEnabled(localTransformServiceEnabled); } thumbnailRegistry.setTransformServiceRegistry(transformServiceRegistry); @@ -257,9 +261,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest // Creates a new source node as the given user in its own transaction. protected NodeRef createSource(String user, String testFileName) { - return AuthenticationUtil.runAs(() -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - createSource(testFileName)), user); + return AuthenticationUtil.runAs(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> createSource(testFileName)), user); } // Creates a new source node as the current user in the current transaction. @@ -271,12 +273,10 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest // Changes the content of a source node as the given user in its own transaction. protected void updateContent(String user, NodeRef sourceNodeRef, String testFileName) { - AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - updateContent(sourceNodeRef, testFileName); - return null; - }), user); + AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + updateContent(sourceNodeRef, testFileName); + return null; + }), user); } // Changes the content of a source node as the current user in the current transaction. @@ -295,12 +295,10 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest // Clears the content of a source node as the given user in its own transaction. protected void clearContent(String user, NodeRef sourceNodeRef) { - AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - clearContent(sourceNodeRef); - return null; - }), user); + AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + clearContent(sourceNodeRef); + return null; + }), user); } // Clears the content of a source node as the current user in the current transaction. @@ -312,23 +310,19 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest // Requests a new rendition as the given user in its own transaction. protected void render(String user, NodeRef sourceNode, String renditionName) { - AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - render(sourceNode, renditionName); - return null; - }), user); + AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + render(sourceNode, renditionName); + return null; + }), user); } // Requests a new metadata extract as the given user in its own transaction. protected void extract(String user, NodeRef sourceNode) { - AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> - transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { - extract(sourceNode); - return null; - }), user); + AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> { + extract(sourceNode); + return null; + }), user); } // Requests a new rendition as the current user in the current transaction. @@ -357,7 +351,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest Throwable cause = e.getCause(); if (cause instanceof AssertionFailedError) { - throw (AssertionFailedError)cause; + throw (AssertionFailedError) cause; } throw e; } @@ -375,7 +369,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest Throwable cause = e.getCause(); if (cause instanceof AssertionFailedError) { - throw (AssertionFailedError)cause; + throw (AssertionFailedError) cause; } throw e; } @@ -386,16 +380,15 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest { long maxMillis = 10000; ChildAssociationRef assoc = null; - for (int i = (int)(maxMillis / 1000); i >= 0; i--) + for (int i = (int) (maxMillis / 1000); i >= 0; i--) { // Must create a new transaction in order to see changes that take place after this method started. - assoc = transactionService.getRetryingTransactionHelper().doInTransaction(() -> - renditionService2.getRenditionByName(sourceNodeRef, renditionName), true, true); + assoc = transactionService.getRetryingTransactionHelper().doInTransaction(() -> renditionService2.getRenditionByName(sourceNodeRef, renditionName), true, true); if (assoc != null) { break; } - logger.debug("RenditionService2.getRenditionByName(...) sleep "+i); + logger.debug("RenditionService2.getRenditionByName(...) sleep " + i); sleep(1000); } if (shouldExist) @@ -415,11 +408,10 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest { long maxMillis = 5000; boolean nodeModified = true; - for (int i = (int)(maxMillis / 1000); i >= 0; i--) + for (int i = (int) (maxMillis / 1000); i >= 0; i--) { // Must create a new transaction in order to see changes that take place after this method started. - nodeModified = transactionService.getRetryingTransactionHelper().doInTransaction(() -> - { + nodeModified = transactionService.getRetryingTransactionHelper().doInTransaction(() -> { Serializable created = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CREATED); Serializable modified = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_MODIFIED); return !created.equals(modified); @@ -428,7 +420,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest { break; } - logger.debug("waitForExtract sleep "+i); + logger.debug("waitForExtract sleep " + i); sleep(1000); } if (nodePropsShouldChange) @@ -445,7 +437,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest protected String getTestFileName(String sourceMimetype) throws FileNotFoundException { String extension = mimetypeMap.getExtension(sourceMimetype); - String testFileName = extension.equals(EXTENSION_BINARY) ? null : "quick."+extension; + String testFileName = extension.equals(EXTENSION_BINARY) ? null : "quick." + extension; if (testFileName != null) { try @@ -491,8 +483,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest String createRandomUser() { - return AuthenticationUtil.runAs(() -> - { + return AuthenticationUtil.runAs(() -> { String username = generateNewUsernameString(); createUser(username); return username; @@ -505,13 +496,12 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest } void createUser(final String username, - final String firstName, - final String lastName, - final String jobTitle, - final long quota) + final String firstName, + final String lastName, + final String jobTitle, + final long quota) { - RetryingTransactionHelper.RetryingTransactionCallback createUserCallback = () -> - { + RetryingTransactionHelper.RetryingTransactionCallback createUserCallback = () -> { authenticationService.createAuthentication(username, PASSWORD.toCharArray()); PropertyMap personProperties = new PropertyMap(); @@ -519,7 +509,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + username); personProperties.put(ContentModel.PROP_FIRSTNAME, firstName); personProperties.put(ContentModel.PROP_LASTNAME, lastName); - personProperties.put(ContentModel.PROP_EMAIL, username+"@example.com"); + personProperties.put(ContentModel.PROP_EMAIL, username + "@example.com"); personProperties.put(ContentModel.PROP_JOBTITLE, jobTitle); if (quota > 0) { @@ -548,14 +538,12 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest } /** - * Helper method which gets the content hash code from the supplied rendition node without specific validations (the - * equivalent method from {@link RenditionService2Impl} is not exposed) + * Helper method which gets the content hash code from the supplied rendition node without specific validations (the equivalent method from {@link RenditionService2Impl} is not exposed) * * @param renditionNodeRef * the rendition node * - * @return -1 in case of there is no content, -2 in case rendition doesn't exist, the actual content hash code - * otherwise + * @return -1 in case of there is no content, -2 in case rendition doesn't exist, the actual content hash code otherwise */ protected int getRenditionContentHashCode(NodeRef renditionNodeRef) { @@ -569,4 +557,28 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest return renditionContentHashCode; } + + /** + * Helper method which gets the content hash code from the supplied source node (the equivalent method from {@link RenditionService2Impl} is not public) + * + * @param sourceNodeRef + * the source node + * + * @return -1 in case of there is no content, otherwise, the actual content hash code otherwise + */ + protected int getSourceContentHashCode(NodeRef sourceNodeRef) + { + int hashCode = -1; + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, nodeService.getProperty(sourceNodeRef, PROP_CONTENT)); + if (contentData != null) + { + // Originally we used the contentData URL, but that is not enough if the mimetype changes. + String contentString = contentData.getContentUrl() + contentData.getMimetype(); + if (contentString != null) + { + hashCode = contentString.hashCode(); + } + } + return hashCode; + } } 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..8807d6724e 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,14 +25,18 @@ */ 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.security.authentication.AuthenticationUtil; @@ -44,8 +48,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 +64,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 +102,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 +152,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 +160,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 +191,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 +201,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati } @Test - public void testCreateRenditionByUser() + public void testCreateRenditionByUser() { String userName = createRandomUser(); NodeRef sourceNodeRef = createSource(userName, "quick.jpg"); @@ -211,13 +211,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 +230,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 +249,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 +277,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 +329,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 +348,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 +360,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 @@ -455,6 +445,61 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati assertEquals(TOTAL_NODES, countModifier(nodes, user1)); } + @Test + public void testForceRenditionsContentHashCode() + { + + // Create a node + NodeRef sourceNodeRef = createSource(ADMIN, "quick.docx"); + assertNotNull("Node not generated", sourceNodeRef); + + // Get content hash code for the source node + int sourceNodeContentHashCode = getSourceContentHashCode(sourceNodeRef); + + // Trigger the pdf rendition + render(ADMIN, sourceNodeRef, PDF); + NodeRef pdfRenditionNodeRef = waitForRendition(ADMIN, sourceNodeRef, PDF, true); + assertNotNull("pdf rendition was not generated", pdfRenditionNodeRef); + assertNotNull("pdf rendition was not generated", nodeService.getProperty(pdfRenditionNodeRef, PROP_CONTENT)); + + // Check the pdf rendition content hash code is valid + int pdfRenditionContentHashCode = getRenditionContentHashCode(pdfRenditionNodeRef); + assertEquals("pdf rendition content hash code is different from source node content hash code", sourceNodeContentHashCode, pdfRenditionContentHashCode); + + // Trigger the doc lib rendition + render(ADMIN, sourceNodeRef, DOC_LIB); + NodeRef docLibRenditionNodeRef = waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); + assertNotNull("doc lib rendition was not generated", docLibRenditionNodeRef); + assertNotNull("doc lib rendition was not generated", nodeService.getProperty(docLibRenditionNodeRef, PROP_CONTENT)); + + // Check the doc lib rendition content hash code is valid + int docLibenditionContentHashCode = getRenditionContentHashCode(docLibRenditionNodeRef); + assertEquals("doc lib rendition content hash code is different from source node content hash code", sourceNodeContentHashCode, docLibenditionContentHashCode); + + // Update the source node content + updateContent(ADMIN, sourceNodeRef, "quick.docx"); + + // Get source node content hash code after update + int sourceNodeContentHashCode2 = getSourceContentHashCode(sourceNodeRef); + + // Check content hash code are different after content update + assertNotEquals("Source node content hash code is the same after content update", sourceNodeContentHashCode, sourceNodeContentHashCode2); + assertNotEquals("pdf rendition content hash code is the same after content update", sourceNodeContentHashCode2, pdfRenditionContentHashCode); + assertNotEquals("doc lib rendition content hash code is the same after content update", sourceNodeContentHashCode2, docLibenditionContentHashCode); + + // Forces the content hash code for every source node renditions + AuthenticationUtil.runAs(() -> { + renditionService2.forceRenditionsContentHashCode(sourceNodeRef); + return null; + }, ADMIN); + + // Check the renditions content hash code are now the same as the latest source node content hash code + int pdfRenditionContentHashCode2 = getRenditionContentHashCode(pdfRenditionNodeRef); + int docLibenditionContentHashCode2 = getRenditionContentHashCode(docLibRenditionNodeRef); + assertEquals("pdf rendition content hash code is different from latest source node content hash code", sourceNodeContentHashCode2, pdfRenditionContentHashCode2); + assertEquals("doc lib rendition content hash code is different from latest source node content hash code", sourceNodeContentHashCode2, docLibenditionContentHashCode2); + } + private int countModifier(List nodes, String user) { int count = 0; @@ -492,22 +537,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 +618,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 +689,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);