[MNT-24992] Add method to force renditions content hash code (#3288) (#3291)

This commit is contained in:
Tiago Salvado
2025-03-31 15:00:23 +01:00
committed by GitHub
parent bb534040a1
commit 8276344ce6
5 changed files with 381 additions and 313 deletions

View File

@@ -1539,7 +1539,7 @@
"filename": "repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java", "filename": "repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false, "is_verified": false,
"line_number": 127, "line_number": 130,
"is_secret": false "is_secret": false
} }
], ],
@@ -1888,5 +1888,5 @@
} }
] ]
}, },
"generated_at": "2025-01-10T19:40:38Z" "generated_at": "2025-03-31T12:42:09Z"
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,35 +25,27 @@
*/ */
package org.alfresco.repo.rendition2; package org.alfresco.repo.rendition2;
import java.util.List;
import org.alfresco.service.NotAuditable; import org.alfresco.service.NotAuditable;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; 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 * The Async Rendition service. Replaces the original rendition services which included synchronous renditions and asynchronous methods with Java call backs.
* asynchronous methods with Java call backs.<p/> * <p/>
* *
* Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated * Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated {@link RenditionDefinitionRegistry2}.
* {@link RenditionDefinitionRegistry2}.<p/> * <p/>
* *
* Unlike the original RenditionService this service, it: * Unlike the original RenditionService this service, it:
* <ul> * <ul>
* <li>Performs async renditions without a Java callback, as another node in the cluster may complete the rendition. * <li>Performs async renditions without a Java callback, as another node in the cluster may complete the rendition. The current node requests a transform, but another node might take the resulting transform and turn it into a rendition if the external Transform Service is used.</li>
* The current node requests a transform, but another node might take the resulting transform and turn it into a * <li>Reduces the configurable options to do with with the associations of rendition nodes, their type. They are identical to 'hidden' (not normally seen as nodes in their own right in a UI) renditions produced by the original service. So, they are always directly under the source node connected by a {@code}rn:rendition{@code} association with the name of the rendition.</li>
* rendition if the external Transform Service is used.</li> * <li>The rendition nodes additionally have a {@code}rn:rendition2{@code} aspect and a {@code}contentUrlHashCode{@code} property. This property contains a value that allows the service to work out if it holds a rendition of the source node's current content.</li>
* <li>Reduces the configurable options to do with with the associations of rendition nodes, their type. They * <li>Failures are handled by setting the rendition node's content to null.</li>
* are identical to 'hidden' (not normally seen as nodes in their own right in a * <li>When a rendition is requested via the REST API, only the newer service is used.</li>
* UI) renditions produced by the original service. So, they are always directly under the source node connected by a * <li>Where possible old service renditions migrate automatically over to the new service when content on a source node is updated.</li>
* {@code}rn:rendition{@code} association with the name of the rendition.</li>
* <li>The rendition nodes additionally have a {@code}rn:rendition2{@code} aspect and a {@code}contentUrlHashCode{@code}
* property. This property contains a value that allows the service to work out if it holds a rendition of the
* source node's current content.</li>
* <li>Failures are handled by setting the rendition node's content to null.</li>
* <li>When a rendition is requested via the REST API, only the newer service is used.</li>
* <li>Where possible old service renditions migrate automatically over to the new service when content on a
* source node is updated.</li>
* </ul> * </ul>
* *
* @author adavis * @author adavis
@@ -66,29 +58,30 @@ public interface RenditionService2
RenditionDefinitionRegistry2 getRenditionDefinitionRegistry2(); RenditionDefinitionRegistry2 getRenditionDefinitionRegistry2();
/** /**
* This method asynchronously transforms content to a target mimetype with transform options supplied in the * 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.
* {@code transformDefinition}. A response is set on a message queue once the transform is complete or fails, * <p>
* together with some client supplied data. The response queue and client data are also included in the
* transformDefinition.<p>
* *
* This method does not create a rendition node, but uses the same code as renditions to perform the transform. The * 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.
* {@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 sourceNodeRef
* @param transformDefinition which defines the transform, where to sent the response and some client specified data. * the node from which the content is retrieved.
* @throws UnsupportedOperationException if the transform is not supported. * @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 @NotAuditable
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition); public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition);
/** /**
* This method asynchronously renders content as specified by the {@code renditionName}. The content to be * This method asynchronously renders content as specified by the {@code renditionName}. The content to be rendered is provided by {@code sourceNodeRef}.
* rendered is provided by {@code sourceNodeRef}.
* *
* @param sourceNodeRef the node from which the content is retrieved. * @param sourceNodeRef
* @param renditionName the rendition to be performed. * the node from which the content is retrieved.
* @throws UnsupportedOperationException if the transform is not supported AND the rendition has not been created before. * @param renditionName
* the rendition to be performed.
* @throws UnsupportedOperationException
* if the transform is not supported AND the rendition has not been created before.
*/ */
@NotAuditable @NotAuditable
public void render(NodeRef sourceNodeRef, String renditionName); 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. * This method gets the rendition of the {@code sourceNodeRef} identified by its name.
* *
* @param sourceNodeRef the source node for the renditions * @param sourceNodeRef
* @param renditionName the renditionName used to identify a rendition. * the source node for the renditions
* @return the {@link ChildAssociationRef} which links the source node to the * @param renditionName
* rendition or <code>null</code> if there is no rendition or it is not up to date. * the renditionName used to identify a rendition.
* @return the {@link ChildAssociationRef} which links the source node to the rendition or <code>null</code> if there is no rendition or it is not up to date.
*/ */
@NotAuditable @NotAuditable
ChildAssociationRef getRenditionByName(NodeRef sourceNodeRef, String renditionName); 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. * 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 @NotAuditable
void clearRenditionContentDataInTransaction(NodeRef renditionNode); void clearRenditionContentDataInTransaction(NodeRef renditionNode);
@@ -124,4 +119,13 @@ public interface RenditionService2
* Indicates if renditions are enabled. Set using the {@code system.thumbnail.generate} value. * Indicates if renditions are enabled. Set using the {@code system.thumbnail.generate} value.
*/ */
boolean isEnabled(); boolean isEnabled();
}
/**
* 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);
}

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,6 +25,24 @@
*/ */
package org.alfresco.repo.rendition2; 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.ContentModel;
import org.alfresco.model.RenditionModel; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.content.ContentServicePolicies; 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.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.PropertyCheck; 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. * The Async Rendition service. Replaces the original deprecated RenditionService.
@@ -95,12 +96,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
abstract RenditionDefinition2 getRenditionDefinition(); abstract RenditionDefinition2 getRenditionDefinition();
void handleUnsupported(UnsupportedOperationException e) void handleUnsupported(UnsupportedOperationException e)
{ {}
}
void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode) void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode)
{ {}
}
} }
private TransactionService transactionService; private TransactionService transactionService;
@@ -217,8 +216,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition) public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition)
{ {
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
{
@Override @Override
public String getName() public String getName()
{ {
@@ -237,8 +235,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void render(NodeRef sourceNodeRef, String renditionName) public void render(NodeRef sourceNodeRef, String renditionName)
{ {
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
{
@Override @Override
public String getName() public String getName()
{ {
@@ -261,7 +258,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void handleUnsupported(UnsupportedOperationException e) 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); NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
if (renditionNode == null) if (renditionNode == null)
{ {
@@ -277,7 +274,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
int renditionContentHashCode = getRenditionContentHashCode(renditionNode); int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode+ " hashCodes"); logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode + " hashCodes");
} }
if (renditionContentHashCode == sourceContentHashCode) if (renditionContentHashCode == sourceContentHashCode)
{ {
@@ -299,14 +296,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
if (!nodeService.exists(sourceNodeRef)) 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(); RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug(renderOrTransform.getName()+ ": transform " +sourceNodeRef); logger.debug(renderOrTransform.getName() + ": transform " + sourceNodeRef);
} }
AtomicBoolean supported = new AtomicBoolean(true); AtomicBoolean supported = new AtomicBoolean(true);
@@ -328,14 +325,13 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
String user = AuthenticationUtil.getRunAsUser(); String user = AuthenticationUtil.getRunAsUser();
RetryingTransactionHelper.RetryingTransactionCallback callback = () -> RetryingTransactionHelper.RetryingTransactionCallback callback = () -> {
{
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
if (!supported.get()) if (!supported.get())
{ {
if (logger.isDebugEnabled()) 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."); "The content might be too big or the source mimetype cannot be converted.");
} }
failure(sourceNodeRef, renditionDefinition, sourceContentHashCode); failure(sourceNodeRef, renditionDefinition, sourceContentHashCode);
@@ -372,26 +368,24 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode) public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode)
{ {
// The original transaction may have already have failed // The original transaction may have already have failed
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
{ return null;
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode); }, false, true));
return null;
}, false, true));
} }
public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition, public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition,
int transformContentHashCode) int transformContentHashCode)
{ {
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
if (logger.isDebugEnabled()) 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) if (renditionDefinition instanceof TransformDefinition)
{ {
TransformDefinition transformDefinition = (TransformDefinition)renditionDefinition; TransformDefinition transformDefinition = (TransformDefinition) renditionDefinition;
String targetMimetype = transformDefinition.getTargetMimetype(); String targetMimetype = transformDefinition.getTargetMimetype();
if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype)) if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype))
{ {
@@ -413,7 +407,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (transformInputStream == null) if (transformInputStream == null)
{ {
@@ -440,7 +434,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (transformInputStream == null) if (transformInputStream == null)
{ {
@@ -468,7 +462,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream, private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (logger.isDebugEnabled()) 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. * 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.
* 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, private void consumeRendition(NodeRef sourceNodeRef, int sourceContentHashCode, InputStream transformInputStream,
RenditionDefinition2 renditionDefinition, int transformContentHashCode) RenditionDefinition2 renditionDefinition, int transformContentHashCode)
{ {
String renditionName = renditionDefinition.getRenditionName(); String renditionName = renditionDefinition.getRenditionName();
if (transformContentHashCode != sourceContentHashCode) 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")); (transformInputStream == null ? " to null as the transform failed" : " to the transform result"));
} }
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
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 try
{ {
ruleService.disableRuleType(RuleType.UPDATE); // Set or replace rendition content
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); 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. ContentReader contentReader = renditionWriter.getReader();
if (createRenditionNode) 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()) if (logger.isDebugEnabled())
{ {
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef); logger.debug("Set rendition hashcode for " + renditionName);
}
}
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;
} }
nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
} }
else else
{ {
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
clearRenditionContentData(renditionNode); clearRenditionContentData(renditionNode);
} }
if (!sourceHasAspectRenditioned)
{
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
}
} }
catch (Exception e) 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 }
{ else
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); {
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); clearRenditionContentData(renditionNode);
ruleService.enableRuleType(RuleType.UPDATE); }
}
return null; if (!sourceHasAspectRenditioned)
}, false, true)); {
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()) 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)) if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_THUMBNAIL_MODIFICATION))
{ {
List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA); List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA);
String target = null; String target = null;
for (String currThumbnailMod: thumbnailMods) for (String currThumbnailMod : thumbnailMods)
{ {
if (currThumbnailMod.startsWith(prefix)) 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 * 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.
* sequences to which they were requested, this is used work out if a rendition should be replaced.
*/ */
private int getSourceContentHashCode(NodeRef sourceNodeRef) private int getSourceContentHashCode(NodeRef sourceNodeRef)
{ {
@@ -675,7 +665,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
if (contentData != null) if (contentData != null)
{ {
// Originally we used the contentData URL, but that is not enough if the mimetype changes. // 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) if (contentString != null)
{ {
hashCode = contentString.hashCode(); 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. * 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.
* 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) 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; 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); Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE);
return hashCode == null return hashCode == null
? SOURCE_HAS_NO_CONTENT ? SOURCE_HAS_NO_CONTENT
: (int)hashCode; : (int) hashCode;
} }
private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName) 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 * This method checks whether the specified source node is of a content class which has been registered for rendition prevention.
* rendition prevention.
* *
* @param sourceNode the node to check. * @param sourceNode
* @throws RenditionService2PreventedException if the source node is configured for rendition prevention. * the node to check.
* @throws RenditionService2PreventedException
* if the source node is configured for rendition prevention.
*/ */
// This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...) // This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...)
private void checkSourceNodeForPreventionClass(NodeRef sourceNode) private void checkSourceNodeForPreventionClass(NodeRef sourceNode)
@@ -823,7 +812,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
for (ChildAssociationRef childAssoc : childAsocs) for (ChildAssociationRef childAssoc : childAsocs)
{ {
NodeRef renditionNode = childAssoc.getChildRef(); NodeRef renditionNode = childAssoc.getChildRef();
if (isRenditionAvailable(sourceNodeRef, renditionNode)) if (isRenditionAvailable(sourceNodeRef, renditionNode))
{ {
result.add(childAssoc); 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 * 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.
* and out of date renditions or those still being created don't have a matching contentHashCode.
*/ */
public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode) public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode)
{ {
@@ -852,7 +840,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
int renditionContentHashCode = getRenditionContentHashCode(renditionNode); int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode+" hashcodes"); logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode + " hashcodes");
} }
if (sourceContentHashCode != renditionContentHashCode) if (sourceContentHashCode != renditionContentHashCode)
{ {
@@ -892,19 +880,17 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
ChildAssociationRef childAssoc = renditions.get(0); ChildAssociationRef childAssoc = renditions.get(0);
NodeRef renditionNode = childAssoc.getChildRef(); NodeRef renditionNode = childAssoc.getChildRef();
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null: childAssoc; return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null : childAssoc;
} }
} }
@Override @Override
public void clearRenditionContentDataInTransaction(NodeRef renditionNode) public void clearRenditionContentDataInTransaction(NodeRef renditionNode)
{ {
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> clearRenditionContentData(renditionNode);
{ return null;
clearRenditionContentData(renditionNode); }, false, true));
return null;
}, false, true));
} }
@Override @Override
@@ -950,4 +936,35 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
} }
@Override
public void forceRenditionsContentHashCode(NodeRef sourceNodeRef)
{
if (sourceNodeRef != null && nodeService.exists(sourceNodeRef))
{
List<ChildAssociationRef> 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);
}
}
}
}
}
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -26,6 +26,7 @@
package org.alfresco.repo.rendition2; package org.alfresco.repo.rendition2;
import static java.lang.Thread.sleep; import static java.lang.Thread.sleep;
import static org.alfresco.model.ContentModel.PROP_CONTENT; import static org.alfresco.model.ContentModel.PROP_CONTENT;
import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE; import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE;
import static org.alfresco.repo.content.MimetypeMap.EXTENSION_BINARY; import static org.alfresco.repo.content.MimetypeMap.EXTENSION_BINARY;
@@ -35,6 +36,15 @@ import java.io.FileNotFoundException;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections; 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.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.metadata.AsynchronousExtractor; 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.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.ChildAssociationRef; 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.ContentReader;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter; 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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef; 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.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService; 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.BaseSpringTest;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap; 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. * 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 ADMIN = "admin";
protected static final String DOC_LIB = "doclib"; protected static final String DOC_LIB = "doclib";
protected static final String PDF = "pdf";
private CronExpression origLocalTransCron; private CronExpression origLocalTransCron;
private CronExpression origRenditionCron; private CronExpression origRenditionCron;
@@ -152,7 +156,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
// Strict MimetypeCheck // Strict MimetypeCheck
System.setProperty("transformer.strict.mimetype.check", "true"); System.setProperty("transformer.strict.mimetype.check", "true");
// Retry on DifferentMimetype // Retry on DifferentMimetype
System.setProperty("content.transformer.retryOn.different.mimetype", "true"); System.setProperty("content.transformer.retryOn.different.mimetype", "true");
} }
@@ -181,7 +185,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
if (transformServiceRegistry instanceof LocalTransformServiceRegistry) if (transformServiceRegistry instanceof LocalTransformServiceRegistry)
{ {
((LocalTransformServiceRegistry)transformServiceRegistry).setEnabled(localTransformServiceEnabled); ((LocalTransformServiceRegistry) transformServiceRegistry).setEnabled(localTransformServiceEnabled);
} }
thumbnailRegistry.setTransformServiceRegistry(transformServiceRegistry); 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. // Creates a new source node as the given user in its own transaction.
protected NodeRef createSource(String user, String testFileName) protected NodeRef createSource(String user, String testFileName)
{ {
return AuthenticationUtil.runAs(() -> return AuthenticationUtil.runAs(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> createSource(testFileName)), user);
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
createSource(testFileName)), user);
} }
// Creates a new source node as the current user in the current transaction. // 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. // Changes the content of a source node as the given user in its own transaction.
protected void updateContent(String user, NodeRef sourceNodeRef, String testFileName) protected void updateContent(String user, NodeRef sourceNodeRef, String testFileName)
{ {
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> updateContent(sourceNodeRef, testFileName);
{ return null;
updateContent(sourceNodeRef, testFileName); }), user);
return null;
}), user);
} }
// Changes the content of a source node as the current user in the current transaction. // 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. // Clears the content of a source node as the given user in its own transaction.
protected void clearContent(String user, NodeRef sourceNodeRef) protected void clearContent(String user, NodeRef sourceNodeRef)
{ {
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> clearContent(sourceNodeRef);
{ return null;
clearContent(sourceNodeRef); }), user);
return null;
}), user);
} }
// Clears the content of a source node as the current user in the current transaction. // 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. // Requests a new rendition as the given user in its own transaction.
protected void render(String user, NodeRef sourceNode, String renditionName) protected void render(String user, NodeRef sourceNode, String renditionName)
{ {
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> render(sourceNode, renditionName);
{ return null;
render(sourceNode, renditionName); }), user);
return null;
}), user);
} }
// Requests a new metadata extract as the given user in its own transaction. // Requests a new metadata extract as the given user in its own transaction.
protected void extract(String user, NodeRef sourceNode) protected void extract(String user, NodeRef sourceNode)
{ {
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> extract(sourceNode);
{ return null;
extract(sourceNode); }), user);
return null;
}), user);
} }
// Requests a new rendition as the current user in the current transaction. // 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(); Throwable cause = e.getCause();
if (cause instanceof AssertionFailedError) if (cause instanceof AssertionFailedError)
{ {
throw (AssertionFailedError)cause; throw (AssertionFailedError) cause;
} }
throw e; throw e;
} }
@@ -375,7 +369,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
Throwable cause = e.getCause(); Throwable cause = e.getCause();
if (cause instanceof AssertionFailedError) if (cause instanceof AssertionFailedError)
{ {
throw (AssertionFailedError)cause; throw (AssertionFailedError) cause;
} }
throw e; throw e;
} }
@@ -386,16 +380,15 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
{ {
long maxMillis = 10000; long maxMillis = 10000;
ChildAssociationRef assoc = null; 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. // Must create a new transaction in order to see changes that take place after this method started.
assoc = transactionService.getRetryingTransactionHelper().doInTransaction(() -> assoc = transactionService.getRetryingTransactionHelper().doInTransaction(() -> renditionService2.getRenditionByName(sourceNodeRef, renditionName), true, true);
renditionService2.getRenditionByName(sourceNodeRef, renditionName), true, true);
if (assoc != null) if (assoc != null)
{ {
break; break;
} }
logger.debug("RenditionService2.getRenditionByName(...) sleep "+i); logger.debug("RenditionService2.getRenditionByName(...) sleep " + i);
sleep(1000); sleep(1000);
} }
if (shouldExist) if (shouldExist)
@@ -415,11 +408,10 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
{ {
long maxMillis = 5000; long maxMillis = 5000;
boolean nodeModified = true; 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. // 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 created = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CREATED);
Serializable modified = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_MODIFIED); Serializable modified = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_MODIFIED);
return !created.equals(modified); return !created.equals(modified);
@@ -428,7 +420,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
{ {
break; break;
} }
logger.debug("waitForExtract sleep "+i); logger.debug("waitForExtract sleep " + i);
sleep(1000); sleep(1000);
} }
if (nodePropsShouldChange) if (nodePropsShouldChange)
@@ -445,7 +437,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
protected String getTestFileName(String sourceMimetype) throws FileNotFoundException protected String getTestFileName(String sourceMimetype) throws FileNotFoundException
{ {
String extension = mimetypeMap.getExtension(sourceMimetype); 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) if (testFileName != null)
{ {
try try
@@ -491,8 +483,7 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
String createRandomUser() String createRandomUser()
{ {
return AuthenticationUtil.runAs(() -> return AuthenticationUtil.runAs(() -> {
{
String username = generateNewUsernameString(); String username = generateNewUsernameString();
createUser(username); createUser(username);
return username; return username;
@@ -505,13 +496,12 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
} }
void createUser(final String username, void createUser(final String username,
final String firstName, final String firstName,
final String lastName, final String lastName,
final String jobTitle, final String jobTitle,
final long quota) final long quota)
{ {
RetryingTransactionHelper.RetryingTransactionCallback<Void> createUserCallback = () -> RetryingTransactionHelper.RetryingTransactionCallback<Void> createUserCallback = () -> {
{
authenticationService.createAuthentication(username, PASSWORD.toCharArray()); authenticationService.createAuthentication(username, PASSWORD.toCharArray());
PropertyMap personProperties = new PropertyMap(); 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_AUTHORITY_DISPLAY_NAME, "title" + username);
personProperties.put(ContentModel.PROP_FIRSTNAME, firstName); personProperties.put(ContentModel.PROP_FIRSTNAME, firstName);
personProperties.put(ContentModel.PROP_LASTNAME, lastName); 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); personProperties.put(ContentModel.PROP_JOBTITLE, jobTitle);
if (quota > 0) 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 * 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)
* equivalent method from {@link RenditionService2Impl} is not exposed)
* *
* @param renditionNodeRef * @param renditionNodeRef
* the rendition node * the rendition node
* *
* @return -1 in case of there is no content, -2 in case rendition doesn't exist, the actual content hash code * @return -1 in case of there is no content, -2 in case rendition doesn't exist, the actual content hash code otherwise
* otherwise
*/ */
protected int getRenditionContentHashCode(NodeRef renditionNodeRef) protected int getRenditionContentHashCode(NodeRef renditionNodeRef)
{ {
@@ -569,4 +557,28 @@ public abstract class AbstractRenditionIntegrationTest extends BaseSpringTest
return renditionContentHashCode; 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;
}
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,14 +25,18 @@
*/ */
package org.alfresco.repo.rendition2; package org.alfresco.repo.rendition2;
import static org.alfresco.model.ContentModel.PROP_CONTENT;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.alfresco.model.ContentModel.PROP_CONTENT;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil; 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.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.junit.BeforeClass;
import org.junit.Test;
/** /**
* Integration tests for {@link RenditionService2} * Integration tests for {@link RenditionService2}
@@ -62,37 +64,37 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
// PDF transformation // PDF transformation
@Test @Test
public void testLocalRenderPdfToJpegMedium() public void testLocalRenderPdfToJpegMedium()
{ {
checkRendition("quick.pdf", "medium", true); checkRendition("quick.pdf", "medium", true);
} }
@Test @Test
public void testLocalRenderPdfToDoclib() public void testLocalRenderPdfToDoclib()
{ {
checkRendition("quick.pdf", "doclib", true); checkRendition("quick.pdf", "doclib", true);
} }
@Test @Test
public void testLocalRenderPdfJpegImgpreview() public void testLocalRenderPdfJpegImgpreview()
{ {
checkRendition("quick.pdf", "imgpreview", true); checkRendition("quick.pdf", "imgpreview", true);
} }
@Test @Test
public void testLocalRenderPdfPngAvatar() public void testLocalRenderPdfPngAvatar()
{ {
checkRendition("quick.pdf", "avatar", true); checkRendition("quick.pdf", "avatar", true);
} }
@Test @Test
public void testLocalRenderPdfPngAvatar32() public void testLocalRenderPdfPngAvatar32()
{ {
checkRendition("quick.pdf", "avatar32", true); checkRendition("quick.pdf", "avatar32", true);
} }
@Test @Test
public void testLocalRenderPdfFlashWebpreview() public void testLocalRenderPdfFlashWebpreview()
{ {
checkRendition("quick.pdf", "webpreview", false); checkRendition("quick.pdf", "webpreview", false);
} }
@@ -100,43 +102,43 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
// DOCX transformation // DOCX transformation
@Test @Test
public void testLocalRenderDocxJpegMedium() public void testLocalRenderDocxJpegMedium()
{ {
checkRendition("quick.docx", "medium", true); checkRendition("quick.docx", "medium", true);
} }
@Test @Test
public void testLocalRenderDocxDoclib() public void testLocalRenderDocxDoclib()
{ {
checkRendition("quick.docx", "doclib", true); checkRendition("quick.docx", "doclib", true);
} }
@Test @Test
public void testLocalRenderDocxJpegImgpreview() public void testLocalRenderDocxJpegImgpreview()
{ {
checkRendition("quick.docx", "imgpreview", true); checkRendition("quick.docx", "imgpreview", true);
} }
@Test @Test
public void testLocalRenderDocxPngAvatar() public void testLocalRenderDocxPngAvatar()
{ {
checkRendition("quick.docx", "avatar", true); checkRendition("quick.docx", "avatar", true);
} }
@Test @Test
public void testLocalRenderDocxPngAvatar32() public void testLocalRenderDocxPngAvatar32()
{ {
checkRendition("quick.docx", "avatar32", true); checkRendition("quick.docx", "avatar32", true);
} }
@Test @Test
public void testLocalRenderDocxFlashWebpreview() public void testLocalRenderDocxFlashWebpreview()
{ {
checkRendition("quick.docx", "webpreview", false); checkRendition("quick.docx", "webpreview", false);
} }
@Test @Test
public void testLocalRenderDocxPdf() public void testLocalRenderDocxPdf()
{ {
checkRendition("quick.docx", "pdf", true); checkRendition("quick.docx", "pdf", true);
} }
@@ -150,7 +152,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void changedSourceToNullContent() public void changedSourceToNullContent()
{ {
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
@@ -158,8 +160,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
clearContent(ADMIN, sourceNodeRef); clearContent(ADMIN, sourceNodeRef);
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false);
assertNull("There should be no rendition as there was no content", assoc); assertNull("There should be no rendition as there was no content", assoc);
} }
@@ -190,8 +191,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
clearContent(ADMIN, sourceNodeRef); clearContent(ADMIN, sourceNodeRef);
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false);
assertNull("There should be no rendition as there was no content", assoc); assertNull("There should be no rendition as there was no content", assoc);
@@ -201,7 +201,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testCreateRenditionByUser() public void testCreateRenditionByUser()
{ {
String userName = createRandomUser(); String userName = createRandomUser();
NodeRef sourceNodeRef = createSource(userName, "quick.jpg"); NodeRef sourceNodeRef = createSource(userName, "quick.jpg");
@@ -211,13 +211,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testReadRenditionByOtherUser() public void testReadRenditionByOtherUser()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
String otherUserName = createRandomUser(); String otherUserName = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true);
return null; return null;
}); });
@@ -231,13 +230,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testRenderByReader() public void testRenderByReader()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
String otherUserName = createRandomUser(); String otherUserName = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true);
return null; return null;
}); });
@@ -251,14 +249,13 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testAccessWithNoPermissions() public void testAccessWithNoPermissions()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
render(ownerUserName, sourceNodeRef, DOC_LIB); render(ownerUserName, sourceNodeRef, DOC_LIB);
String noPermissionsUser = createRandomUser(); String noPermissionsUser = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, noPermissionsUser, PermissionService.ALL_PERMISSIONS, false); permissionService.setPermission(sourceNodeRef, noPermissionsUser, PermissionService.ALL_PERMISSIONS, false);
return null; return null;
}); });
@@ -280,12 +277,9 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName));
NodeRef oldRendition = AuthenticationUtil.runAs(() -> NodeRef oldRendition = AuthenticationUtil.runAs(() -> renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName);
renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName);
assertFalse("The rendition should be generated by old Rendition Service", assertFalse("The rendition should be generated by old Rendition Service",
AuthenticationUtil.runAs(() -> nodeService.hasAspect(oldRendition, RenditionModel.ASPECT_RENDITION2), ownerUserName)); AuthenticationUtil.runAs(() -> nodeService.hasAspect(oldRendition, RenditionModel.ASPECT_RENDITION2), ownerUserName));
@@ -335,12 +329,10 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
renditionService2.setEnabled(false); renditionService2.setEnabled(false);
// Call 'clearRenditionContentData' method directly to prove rendition content will be cleaned // Call 'clearRenditionContentData' method directly to prove rendition content will be cleaned
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB);
{ return null;
renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB); }), ADMIN);
return null;
}), ADMIN);
// The rendition should not have content by now // The rendition should not have content by now
assertNull("Rendition has content", nodeService.getProperty(renditionNodeRef, ContentModel.PROP_CONTENT)); 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. * Tests if a rendition without content (but with contentHashCode) can be generated again.
* <p> * <p>
* If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the * If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the rendition node, allowing new requests to generate the rendition.
* rendition node, allowing new requests to generate the rendition.
* </p> * </p>
*/ */
@Test @Test
@@ -369,8 +360,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
/** /**
* Tests if a rendition without content (but with contentHashCode) can be generated again. * Tests if a rendition without content (but with contentHashCode) can be generated again.
* <p> * <p>
* If the rendition consumption receives a zero length InputStream, the contentHashCode should be cleaned from the * 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.
* rendition node, allowing new requests to generate the rendition.
* </p> * </p>
*/ */
@Test @Test
@@ -455,6 +445,61 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
assertEquals(TOTAL_NODES, countModifier(nodes, user1)); 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<NodeRef> nodes, String user) private int countModifier(List<NodeRef> nodes, String user)
{ {
int count = 0; int count = 0;
@@ -492,22 +537,16 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
assertNotNull("The old renditions service did not render", waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true)); assertNotNull("The old renditions service did not render", waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true));
List<String> lastThumbnailModification = transactionService.getRetryingTransactionHelper() List<String> lastThumbnailModification = transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
AuthenticationUtil.runAs(() ->
(List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
updateContent(ADMIN, sourceNodeRef, "quick.png"); updateContent(ADMIN, sourceNodeRef, "quick.png");
List<String> newThumbnailModification = null; List<String> newThumbnailModification = null;
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
newThumbnailModification = transactionService.getRetryingTransactionHelper() newThumbnailModification = transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
AuthenticationUtil.runAs(() ->
(List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
if (!newThumbnailModification.equals(lastThumbnailModification)) if (!newThumbnailModification.equals(lastThumbnailModification))
{ {
break; break;
@@ -579,9 +618,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true);
renditionService2.setEnabled(true); renditionService2.setEnabled(true);
@@ -652,9 +689,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true);
renditionService2.setEnabled(true); renditionService2.setEnabled(true);