Merged DEV/SG/MNT15135 to 5.2.N

MNT-15135 "Alfresco Media Management: Rendition Concurrency Failure on Property update"

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@130692 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Steven Glover
2016-09-15 13:06:35 +00:00
parent f5a2ec747b
commit e83e9f4bc1
6 changed files with 1217 additions and 571 deletions

View File

@@ -122,6 +122,15 @@
<property name="contentService"> <property name="contentService">
<ref bean="ContentService" /> <ref bean="ContentService" />
</property> </property>
<property name="versionService">
<ref bean="versionService" />
</property>
<property name="attributeService">
<ref bean="AttributeService" />
</property>
<property name="transactionService">
<ref bean="transactionService" />
</property>
<property name="applicableTypes"> <property name="applicableTypes">
<list> <list>
<value>{http://www.alfresco.org/model/content/1.0}content</value> <value>{http://www.alfresco.org/model/content/1.0}content</value>

View File

@@ -36,6 +36,7 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
@@ -53,12 +54,15 @@ import org.alfresco.repo.rendition.RenditionLocation;
import org.alfresco.repo.rendition.RenditionLocationResolver; import org.alfresco.repo.rendition.RenditionLocationResolver;
import org.alfresco.repo.rendition.RenditionNodeManager; import org.alfresco.repo.rendition.RenditionNodeManager;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionSummary; import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenderCallback; import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.rendition.RenditionDefinition;
@@ -71,10 +75,15 @@ import org.alfresco.service.cmr.repository.ContentWriter;
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.SerializedTransformationOptionsAccessor; import org.alfresco.service.cmr.repository.SerializedTransformationOptionsAccessor;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.namespace.NamespaceException;
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.alfresco.service.transaction.TransactionService;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
import org.alfresco.util.transaction.TransactionListener;
import org.alfresco.util.transaction.TransactionSupportUtil;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.I18NUtil;
@@ -89,12 +98,14 @@ import com.sun.star.lang.NullPointerException;
* @author Nick Smith * @author Nick Smith
* @since 3.3 * @since 3.3
*/ */
public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase implements TransactionListener
{ {
/** Logger */ /** Logger */
private static Log logger = LogFactory.getLog(AbstractRenderingEngine.class); private static Log logger = LogFactory.getLog(AbstractRenderingEngine.class);
private static final String RENDITIONED_CONTENT = "RENDITIONED_CONTENT";
private static final String RENDERING_CONTEXTS = "RenderingEngine.Contexts";
protected static final String CONTENT_READER_NOT_FOUND_MESSAGE = "Cannot find Content Reader for document. Operation can't be performed"; protected static final String CONTENT_READER_NOT_FOUND_MESSAGE = "Cannot find Content Reader for document. Operation can't be performed";
protected static final String DEFAULT_RUN_AS_NAME = AuthenticationUtil.getSystemUserName(); protected static final String DEFAULT_RUN_AS_NAME = AuthenticationUtil.getSystemUserName();
@@ -145,6 +156,9 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
protected ContentService contentService; protected ContentService contentService;
protected MimetypeMap mimetypeMap; protected MimetypeMap mimetypeMap;
protected ActionTrackingService actionTrackingService; protected ActionTrackingService actionTrackingService;
protected AttributeService attributeService;
protected TransactionService transactionService;
protected VersionService versionService;
/* Parameter names common to all Rendering Actions */ /* Parameter names common to all Rendering Actions */
/** /**
@@ -216,6 +230,21 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
private final NodeLocator temporaryParentNodeLocator; private final NodeLocator temporaryParentNodeLocator;
private final QName temporaryRenditionLinkType; private final QName temporaryRenditionLinkType;
public void setVersionService(VersionService versionService)
{
this.versionService = versionService;
}
public void setAttributeService(AttributeService attributeService)
{
this.attributeService = attributeService;
}
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/** /**
* Injects the nodeService bean. * Injects the nodeService bean.
* *
@@ -527,7 +556,9 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
RenderingContext context = new RenderingContext(sourceNode, RenderingContext context = new RenderingContext(sourceNode,
renditionDefinition, renditionDefinition,
targetContentProp); targetContentProp);
render(context); render(context);
// This is a workaround for the fact that actions don't have return // This is a workaround for the fact that actions don't have return
// values. // values.
action.getParameterValues().put(PARAM_RESULT, context.getChildAssociationRef()); action.getParameterValues().put(PARAM_RESULT, context.getChildAssociationRef());
@@ -760,12 +791,24 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
return result; return result;
} }
public static String getRenderedContentKey(NodeRef sourceNode, VersionService versionService)
{
StringBuilder sb = new StringBuilder();
sb.append(sourceNode.toString());
sb.append(".");
Version version = versionService.getCurrentVersion(sourceNode);
sb.append(version != null ? version.getVersionLabel() : "1.0");
return sb.toString();
}
protected class RenderingContext implements SerializedTransformationOptionsAccessor protected class RenderingContext implements SerializedTransformationOptionsAccessor
{ {
private final String guid = GUID.generate();
private final NodeRef sourceNode; private final NodeRef sourceNode;
private final RenditionDefinition definition; private final RenditionDefinition definition;
private final QName renditionContentProperty; private final QName renditionContentProperty;
private String renderedContentKey;
private ChildAssociationRef caNodeRef; private ChildAssociationRef caNodeRef;
/** /**
@@ -773,13 +816,95 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
* @param definition RenditionDefinition * @param definition RenditionDefinition
* @param renditionContentProperty QName * @param renditionContentProperty QName
*/ */
public RenderingContext(NodeRef sourceNode,// RenderingContext(NodeRef sourceNode, RenditionDefinition definition, QName renditionContentProperty)
RenditionDefinition definition,//
QName renditionContentProperty)
{ {
this.sourceNode = sourceNode; this.sourceNode = sourceNode;
this.definition = definition; this.definition = definition;
this.renditionContentProperty = renditionContentProperty; this.renditionContentProperty = renditionContentProperty;
this.renderedContentKey = AbstractRenderingEngine.getRenderedContentKey(sourceNode, versionService);
}
public String getRenderedContentKey()
{
return renderedContentKey;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((guid == null) ? 0 : guid.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RenderingContext other = (RenderingContext) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (guid == null) {
if (other.guid != null)
return false;
} else if (!guid.equals(other.guid))
return false;
return true;
}
/**
* Save an existing transformed content url for this rendition and node
*
* @param existingTransformedContentUrl
*/
void setExistingTransformedContentUrl(final String existingTransformedContentUrl)
{
// add the rendering context to the txn listener so that we can clean up
// any rendering context data saved using the AttributeService
TransactionalResourceHelper.getSet(RENDERING_CONTEXTS).add(this);
TransactionSupportUtil.bindListener(AbstractRenderingEngine.this, 0);
if(logger.isDebugEnabled())
{
logger.debug("setExistingTransformedContentUrl for renderedContentKey " + renderedContentKey
+ ", rendition " + getDefinition().getRenditionName());
}
// make sure this is saved in a new transaction
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
attributeService.setAttribute(existingTransformedContentUrl, RENDITIONED_CONTENT, renderedContentKey,
getDefinition().getRenditionName());
return null;
}
}, false, true);
}
/**
* Get an existing transformed content/rendition url for this rendition and node
*
* @return content url of transformed content/rendition
*/
String getExistingTransformedContentUrl()
{
// add the rendering context to the txn listener so that we can clean up
// any rendering context data saved using the AttributeService
TransactionalResourceHelper.getSet(RENDERING_CONTEXTS).add(this);
TransactionSupportUtil.bindListener(AbstractRenderingEngine.this, 0);
String contentUrl = (String)attributeService.getAttribute(RENDITIONED_CONTENT, renderedContentKey,
getDefinition().getRenditionName());
return contentUrl;
} }
/** /**
@@ -800,6 +925,7 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
{ {
this.caNodeRef = createRenditionNodeAssoc(sourceNode, definition); this.caNodeRef = createRenditionNodeAssoc(sourceNode, definition);
} }
return this.caNodeRef; return this.caNodeRef;
} }
@@ -861,8 +987,22 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
return number.intValue(); return number.intValue();
} }
} }
@Override
public String toString()
{
return "RenderingContext [sourceNode=" + sourceNode + ", renderedContentKey=" + renderedContentKey
+ ", definition=" + definition + ", renditionContentProperty=" + renditionContentProperty + ", caNodeRef=" + caNodeRef
+ ", actionDefinitionName=" + definition.getActionDefinitionName()
+ ", renditionName=" + definition.getRenditionName()
+ "]";
} }
private AbstractRenderingEngine getOuterType()
{
return AbstractRenderingEngine.this;
}
}
protected void tagSourceNodeAsRenditioned(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef) protected void tagSourceNodeAsRenditioned(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef)
{ {
@@ -1121,4 +1261,68 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
} }
return executionSummaries.iterator().next(); return executionSummaries.iterator().next();
} }
/**
* {@inheritDoc}
*/
@Override
public void beforeCommit(boolean readOnly)
{
}
/**
* {@inheritDoc}
*/
@Override
public void beforeCompletion()
{
}
/**
* {@inheritDoc}
*/
@Override
public void afterCommit()
{
// clear saved rendition data if we are successful
final Set<RenderingContext> renderingContexts = TransactionalResourceHelper.getSet(RENDERING_CONTEXTS);
if(logger.isDebugEnabled())
{
logger.debug("Cleaning up " + renderingContexts.size() + " rendering contexts");
}
if(!renderingContexts.isEmpty())
{
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
for(RenderingContext context : renderingContexts)
{
if(logger.isDebugEnabled())
{
logger.debug("Cleaning up rendering context for source node " + context.getSourceNode()
+ ", rendition " + context.getDefinition().getRenditionName());
}
attributeService.removeAttributes(RENDITIONED_CONTENT, context.getRenderedContentKey(),
context.getDefinition().getRenditionName());
}
return null;
}
}, false, true);
}
}
/**
* {@inheritDoc}
*/
@Override
public void afterRollback()
{
}
} }

View File

@@ -36,6 +36,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.TransformerConfig; import org.alfresco.repo.content.transform.TransformerConfig;
import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.content.transform.TransformerDebug;
@@ -56,13 +59,17 @@ import org.alfresco.service.cmr.repository.TransformationOptionLimits;
import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions; import org.alfresco.service.cmr.repository.TransformationSourceOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions.TransformationSourceOptionsSerializer; import org.alfresco.service.cmr.repository.TransformationSourceOptions.TransformationSourceOptionsSerializer;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/** /**
* @author Nick Smith * @author Nick Smith
*/ */
public abstract class AbstractTransformationRenderingEngine extends AbstractRenderingEngine public abstract class AbstractTransformationRenderingEngine extends AbstractRenderingEngine implements ApplicationContextAware
{ {
private static Log logger = LogFactory.getLog(AbstractTransformationRenderingEngine.class); private static Log logger = LogFactory.getLog(AbstractTransformationRenderingEngine.class);
@@ -113,6 +120,8 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
*/ */
public static final String PARAM_USE = TransformerConfig.USE.replaceAll("\\.", ""); public static final String PARAM_USE = TransformerConfig.USE.replaceAll("\\.", "");
private ApplicationContext applicationContext;
/* Error messages */ /* Error messages */
private static final String TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN = "Transformer for '%s' source mime type and '%s' target mime type was not found. Operation can't be performed"; private static final String TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN = "Transformer for '%s' source mime type and '%s' target mime type was not found. Operation can't be performed";
private static final String NOT_TRANSFORMABLE_MESSAGE_PATTERN = "Content not transformable for '%s' source mime type and '%s' target mime type. Operation can't be performed"; private static final String NOT_TRANSFORMABLE_MESSAGE_PATTERN = "Content not transformable for '%s' source mime type and '%s' target mime type. Operation can't be performed";
@@ -120,11 +129,18 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
private Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers; private Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers;
private ContentStore tempStore;
public Collection<TransformationSourceOptionsSerializer> getSourceOptionsSerializers() public Collection<TransformationSourceOptionsSerializer> getSourceOptionsSerializers()
{ {
return sourceOptionsSerializers; return sourceOptionsSerializers;
} }
public void setApplicationContext(ApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
}
public void setSourceOptionsSerializers(Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers) public void setSourceOptionsSerializers(Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers)
{ {
this.sourceOptionsSerializers = sourceOptionsSerializers; this.sourceOptionsSerializers = sourceOptionsSerializers;
@@ -158,6 +174,15 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
{ {
executorService = Executors.newCachedThreadPool(); executorService = Executors.newCachedThreadPool();
} }
this.tempStore = new FileContentStore(this.applicationContext, TempFileProvider.getTempDir().getAbsolutePath());
}
private void copyTransformedContent(ContentReader transformedContentReader, RenderingContext context)
{
// Copy content from temp writer to real writer
ContentWriter writer = context.makeContentWriter();
writer.putContent(transformedContentReader.getContentInputStream());
} }
/* /*
@@ -167,6 +192,26 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
@Override @Override
protected void render(RenderingContext context) protected void render(RenderingContext context)
{ {
// check for existing transformed content and reuse if present to avoid re-rendering
String existingTransformedContentUrl = context.getExistingTransformedContentUrl();
if(existingTransformedContentUrl != null)
{
if(logger.isDebugEnabled())
{
logger.debug("Reusing existing rendered content " + existingTransformedContentUrl
+ " for rendering context " + context);
}
ContentReader existingTransformedContent = tempStore.getReader(existingTransformedContentUrl);
copyTransformedContent(existingTransformedContent, context);
}
else
{
if(logger.isDebugEnabled())
{
logger.debug("Rendering for rendering context " + context);
}
ContentReader contentReader = context.makeContentReader(); ContentReader contentReader = context.makeContentReader();
// There will have been an exception if there is no content data so contentReader is not null. // There will have been an exception if there is no content data so contentReader is not null.
String sourceUrl = contentReader.getContentUrl(); String sourceUrl = contentReader.getContentUrl();
@@ -288,8 +333,6 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
if (actionCompleted) if (actionCompleted)
{ {
// Copy content from temp writer to real writer
ContentWriter writer = context.makeContentWriter();
try try
{ {
// We should not need another timeout here, things should be ready for us // We should not need another timeout here, things should be ready for us
@@ -299,7 +342,11 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
// We should never be in this state, but just in case // We should never be in this state, but just in case
throw new RenditionServiceException("Target of transformation not present"); throw new RenditionServiceException("Target of transformation not present");
} }
writer.putContent(tempTarget.getReader().getContentInputStream());
// save transform result in case we need to retry
context.setExistingTransformedContentUrl(tempTarget.getReader().getContentUrl());
copyTransformedContent(tempTarget.getReader(), context);
} }
catch (ExecutionException e) catch (ExecutionException e)
{ {
@@ -318,6 +365,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
} }
} }
} }
}
protected abstract TransformationOptions getTransformOptions(RenderingContext context); protected abstract TransformationOptions getTransformOptions(RenderingContext context);
@@ -440,7 +488,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
public ContentWriter doWork() throws Exception public ContentWriter doWork() throws Exception
{ {
// ALF-15715: Use temporary write to avoid operating on the real node for fear of row locking while long transforms are in progress // ALF-15715: Use temporary write to avoid operating on the real node for fear of row locking while long transforms are in progress
ContentWriter tempContentWriter = contentService.getTempWriter(); ContentWriter tempContentWriter = tempStore.getWriter(ContentContext.NULL_CONTEXT);
tempContentWriter.setMimetype(targetMimeType); tempContentWriter.setMimetype(targetMimeType);
try try
{ {

View File

@@ -265,8 +265,8 @@ public class ThumbnailServiceImpl implements ThumbnailService,
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("Thumbnail created " + childAssoc + " for sourceNodeRef " + sourceNodeRef + ", thumbnail " + thumbnailName logger.debug("Thumbnail created " + childAssoc + " for sourceNodeRef " + sourceNodeRef + ", thumbnail "
+ ", thumbnailNodeRef " + thumbnailNodeRef); + thumbnailName + ", thumbnailNodeRef " + thumbnailNodeRef);
} }
// MNT-15135: Cache the associations between parent nodes and updated thumbnails, // MNT-15135: Cache the associations between parent nodes and updated thumbnails,
@@ -382,6 +382,12 @@ public class ThumbnailServiceImpl implements ThumbnailService,
//We can be in a read-only transaction, so force a new transaction //We can be in a read-only transaction, so force a new transaction
requiresNew = true; requiresNew = true;
} }
// Get the name of the thumbnail and add to properties map
QName thumbnailQName = getThumbnailQName(thumbnailName);
final RenditionDefinition definition = createRenditionDefinition(contentProperty, mimetype,
transformationOptions, thumbnailQName, assocDetails);
return txnHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>() return txnHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>()
{ {
@@ -392,18 +398,12 @@ public class ThumbnailServiceImpl implements ThumbnailService,
{ {
public NodeRef doWork() throws Exception public NodeRef doWork() throws Exception
{ {
return createThumbnailNode( node, return createThumbnailNode(node, definition, thumbnailName);
contentProperty,
mimetype,
transformationOptions,
thumbnailName,
assocDetails);
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
}, false, requiresNew); }, false, requiresNew);
} }
private QName getThumbnailQName(String localThumbnailName) private QName getThumbnailQName(String localThumbnailName)
@@ -717,14 +717,8 @@ public class ThumbnailServiceImpl implements ThumbnailService,
return definition; return definition;
} }
private NodeRef createThumbnailNode(final NodeRef node, final QName contentProperty, private NodeRef createThumbnailNode(final NodeRef node, final RenditionDefinition definition, final String thumbnailName)
final String mimetype, final TransformationOptions transformationOptions, final String thumbnailName,
final ThumbnailParentAssociationDetails assocDetails)
{ {
// Get the name of the thumbnail and add to properties map
QName thumbnailQName = getThumbnailQName(thumbnailName);
RenditionDefinition definition = createRenditionDefinition(contentProperty, mimetype,
transformationOptions, thumbnailQName, assocDetails);
try try
{ {
ChildAssociationRef thumbnailAssoc = renditionService.render(node, definition); ChildAssociationRef thumbnailAssoc = renditionService.render(node, definition);

View File

@@ -41,7 +41,6 @@ import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.RuntimeActionService; import org.alfresco.repo.action.RuntimeActionService;
@@ -63,6 +62,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.lock.LockService; import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.lock.LockType;
@@ -86,9 +86,11 @@ import org.alfresco.service.cmr.repository.ScriptLocation;
import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.cmr.version.VersionService;
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.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory; import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
@@ -2615,6 +2617,13 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
{ {
private static final String ENGINE_NAME = "helloWorldRenderingEngine"; private static final String ENGINE_NAME = "helloWorldRenderingEngine";
public DummyHelloWorldRenditionEngine(ConfigurableApplicationContext ctx)
{
this.transactionService = (TransactionService)ctx.getBean("transactionService");
this.attributeService = (AttributeService)ctx.getBean("attributeService");
this.versionService = (VersionService)ctx.getBean("versionService");
}
/** /**
* Loads this executor into the ApplicationContext, if it * Loads this executor into the ApplicationContext, if it
* isn't already there * isn't already there
@@ -2624,7 +2633,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
if(!ctx.containsBean(ENGINE_NAME)) if(!ctx.containsBean(ENGINE_NAME))
{ {
// Create, and do dependencies // Create, and do dependencies
DummyHelloWorldRenditionEngine hw = new DummyHelloWorldRenditionEngine(); DummyHelloWorldRenditionEngine hw = new DummyHelloWorldRenditionEngine(ctx);
hw.setRuntimeActionService( hw.setRuntimeActionService(
(RuntimeActionService)ctx.getBean("actionService") (RuntimeActionService)ctx.getBean("actionService")
); );
@@ -2653,7 +2662,8 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
} }
@Override @Override
protected void render(RenderingContext context) { protected void render(RenderingContext context)
{
ContentWriter contentWriter = context.makeContentWriter(); ContentWriter contentWriter = context.makeContentWriter();
contentWriter.setMimetype("text/plain"); contentWriter.setMimetype("text/plain");
contentWriter.putContent( "Hello, world!" ); contentWriter.putContent( "Hello, world!" );

View File

@@ -27,8 +27,15 @@
package org.alfresco.repo.thumbnail; package org.alfresco.repo.thumbnail;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
@@ -47,12 +54,16 @@ import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageResizeOptions;
import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.jscript.ClasspathScriptLocation;
import org.alfresco.repo.rendition.executer.AbstractRenderingEngine;
import org.alfresco.repo.rendition.executer.ImageRenderingEngine;
import org.alfresco.repo.thumbnail.script.ScriptThumbnailService; import org.alfresco.repo.thumbnail.script.ScriptThumbnailService;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry; import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionCondition;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
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.ContentData;
@@ -71,6 +82,7 @@ import org.alfresco.service.cmr.rule.RuleType;
import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo; import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo;
import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails;
import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.cmr.version.VersionService;
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.alfresco.service.namespace.QNamePattern; import org.alfresco.service.namespace.QNamePattern;
@@ -80,6 +92,7 @@ import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.TempFileProvider; import org.alfresco.util.TempFileProvider;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.junit.experimental.categories.Category; import org.junit.experimental.categories.Category;
@@ -102,6 +115,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
private ScriptService scriptService; private ScriptService scriptService;
private MimetypeMap mimetypeMap; private MimetypeMap mimetypeMap;
private TransactionService transactionService; private TransactionService transactionService;
private VersionService versionService;
private AttributeService attributeService;
private ServiceRegistry services; private ServiceRegistry services;
private FailureHandlingOptions failureHandlingOptions; private FailureHandlingOptions failureHandlingOptions;
@@ -127,8 +142,10 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
this.scriptThumbnailService = (ScriptThumbnailService) this.applicationContext.getBean("thumbnailServiceScript"); this.scriptThumbnailService = (ScriptThumbnailService) this.applicationContext.getBean("thumbnailServiceScript");
this.mimetypeMap = (MimetypeMap) this.applicationContext.getBean("mimetypeService"); this.mimetypeMap = (MimetypeMap) this.applicationContext.getBean("mimetypeService");
this.scriptService = (ScriptService) this.applicationContext.getBean("ScriptService"); this.scriptService = (ScriptService) this.applicationContext.getBean("ScriptService");
this.attributeService = (AttributeService) this.applicationContext.getBean("attributeService");
this.services = (ServiceRegistry) this.applicationContext.getBean("ServiceRegistry"); this.services = (ServiceRegistry) this.applicationContext.getBean("ServiceRegistry");
this.transactionService = (TransactionService) this.applicationContext.getBean("transactionService"); this.transactionService = (TransactionService) this.applicationContext.getBean("transactionService");
this.versionService = (VersionService) this.applicationContext.getBean("versionService");
this.failureHandlingOptions = (FailureHandlingOptions) this.applicationContext.getBean("standardFailureOptions"); this.failureHandlingOptions = (FailureHandlingOptions) this.applicationContext.getBean("standardFailureOptions");
// Create a folder and some content // Create a folder and some content
@@ -169,7 +186,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
{ {
QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition( final ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(
qname.getLocalName()); qname.getLocalName());
assertEquals("doclib", details.getName()); assertEquals("doclib", details.getName());
assertEquals("image/png", details.getMimetype()); assertEquals("image/png", details.getMimetype());
@@ -177,21 +194,43 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
checkTransformer(); checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); final NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, setComplete();
endTransaction();
final NodeRef thumbnail0 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib"); MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
return thumbnail;
}
}, false, true);
assertNotNull(thumbnail0); assertNotNull(thumbnail0);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1))); checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition("doclib", thumbnail0); checkRendition(jpgOrig, "doclib", thumbnail0);
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test");
return null;
}
}, false, true);
} }
public void testCreateRenditionThumbnailFromPdf() throws Exception public void testCreateRenditionThumbnailFromPdf() throws Exception
{ {
QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition( final ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(
qname.getLocalName()); qname.getLocalName());
assertEquals("doclib", details.getName()); assertEquals("doclib", details.getName());
assertEquals("image/png", details.getMimetype()); assertEquals("image/png", details.getMimetype());
@@ -199,14 +238,36 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
checkTransformer(); checkTransformer();
NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF); final NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT, setComplete();
endTransaction();
final NodeRef thumbnail0 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail = thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib"); MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
return thumbnail;
}
}, false, true);
assertNotNull(thumbnail0); assertNotNull(thumbnail0);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1))); checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition("doclib", thumbnail0); checkRendition(pdfOrig, "doclib", thumbnail0);
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test");
return null;
}
}, false, true);
} }
public void testCreateRenditionThumbnailFromPdfPage2() throws Exception public void testCreateRenditionThumbnailFromPdfPage2() throws Exception
@@ -222,119 +283,228 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
checkTransformer(); checkTransformer();
NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF); final NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, thumbnailDefinition.getTransformationOptions(), "doclib_2"); MimetypeMap.MIMETYPE_IMAGE_JPEG, thumbnailDefinition.getTransformationOptions(), "doclib_2");
setComplete();
endTransaction();
assertNotNull(thumbnail0); assertNotNull(thumbnail0);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib_2", 1))); checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib_2", 1)));
checkRendition("doclib_2", thumbnail0); checkRendition(pdfOrig, "doclib_2", thumbnail0);
// Check the length // Check the length
File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", ".jpg"); File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", ".jpg");
ContentReader reader = this.contentService.getReader(thumbnail0, ContentModel.PROP_CONTENT); ContentReader reader = contentService.getReader(thumbnail0, ContentModel.PROP_CONTENT);
long size = reader.getSize(); long size = reader.getSize();
System.out.println("size=" + size); System.out.println("size=" + size);
assertTrue("Page 2 should be blank and less than 4500 bytes", size < 4500); assertTrue("Page 2 should be blank and less than 4500 bytes", size < 4500);
reader.getContent(tempFile); reader.getContent(tempFile);
System.out.println("doclib_2 test: " + tempFile.getPath());
return null;
}
}, false, true);
} }
public void testCreateThumbnailFromImage() throws Exception public void testCreateThumbnailFromImage() throws Exception
{ {
checkTransformer(); checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); final NodeRef jpgOrig = createOriginalContent(folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
NodeRef gifOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF); final NodeRef gifOrig = createOriginalContent(folder, MimetypeMap.MIMETYPE_IMAGE_GIF);
setComplete();
endTransaction();
// ===== small: 64x64, marked as thumbnail ==== // ===== small: 64x64, marked as thumbnail ====
ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); final ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64); imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64); imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true); imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions); imageTransformationOptions.setResizeOptions(imageResizeOptions);
// ThumbnailDetails createOptions = new ThumbnailDetails();
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail1 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
return thumbnail1;
}
}, false, true);
assertNotNull(thumbnail1); assertNotNull(thumbnail1);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1))); checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1)));
checkRendition("small", thumbnail1); checkRendition(jpgOrig, "small", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "jpg", "small - 64x64, marked as thumbnail"); outputThumbnailTempContentLocation(thumbnail1, "jpg", "small - 64x64, marked as thumbnail");
return null;
}
}, false, true);
// ===== small2: 64x64, aspect not maintained ==== // ===== small2: 64x64, aspect not maintained ====
ImageResizeOptions imageResizeOptions2 = new ImageResizeOptions(); final ImageResizeOptions imageResizeOptions2 = new ImageResizeOptions();
imageResizeOptions2.setWidth(64); imageResizeOptions2.setWidth(64);
imageResizeOptions2.setHeight(64); imageResizeOptions2.setHeight(64);
imageResizeOptions2.setMaintainAspectRatio(false); imageResizeOptions2.setMaintainAspectRatio(false);
ImageTransformationOptions imageTransformationOptions2 = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions2 = new ImageTransformationOptions();
imageTransformationOptions2.setResizeOptions(imageResizeOptions2); imageTransformationOptions2.setResizeOptions(imageResizeOptions2);
// ThumbnailDetails createOptions2 = new ThumbnailDetails();
NodeRef thumbnail2 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail2 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail2 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions2, "small2"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions2, "small2");
return thumbnail2;
}
}, false, true);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small2", 1))); checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small2", 1)));
checkRendition("small2", thumbnail2); checkRendition(jpgOrig, "small2", thumbnail2);
outputThumbnailTempContentLocation(thumbnail2, "jpg", "small2 - 64x64, aspect not maintained"); outputThumbnailTempContentLocation(thumbnail2, "jpg", "small2 - 64x64, aspect not maintained");
return null;
}
}, false, true);
// ===== half: 50%x50 ===== // ===== half: 50%x50 =====
ImageResizeOptions imageResizeOptions3 = new ImageResizeOptions(); final ImageResizeOptions imageResizeOptions3 = new ImageResizeOptions();
imageResizeOptions3.setWidth(50); imageResizeOptions3.setWidth(50);
imageResizeOptions3.setHeight(50); imageResizeOptions3.setHeight(50);
imageResizeOptions3.setPercentResize(true); imageResizeOptions3.setPercentResize(true);
ImageTransformationOptions imageTransformationOptions3 = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions3 = new ImageTransformationOptions();
imageTransformationOptions3.setResizeOptions(imageResizeOptions3); imageTransformationOptions3.setResizeOptions(imageResizeOptions3);
// ThumbnailDetails createOptions3 = new ThumbnailDetails();
NodeRef thumbnail3 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail3 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail3 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions3, "half"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions3, "half");
return thumbnail3;
}
}, false, true);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half", 1))); Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half", 1)));
checkRendition("half", thumbnail3); checkRendition(jpgOrig, "half", thumbnail3);
outputThumbnailTempContentLocation(thumbnail3, "jpg", "half - 50%x50%"); outputThumbnailTempContentLocation(thumbnail3, "jpg", "half - 50%x50%");
return null;
}
}, false, true);
// ===== half2: 50%x50 from gif ===== // ===== half2: 50%x50 from gif =====
ImageResizeOptions imageResizeOptions4 = new ImageResizeOptions(); final ImageResizeOptions imageResizeOptions4 = new ImageResizeOptions();
imageResizeOptions4.setWidth(50); imageResizeOptions4.setWidth(50);
imageResizeOptions4.setHeight(50); imageResizeOptions4.setHeight(50);
imageResizeOptions4.setPercentResize(true); imageResizeOptions4.setPercentResize(true);
ImageTransformationOptions imageTransformationOptions4 = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions4 = new ImageTransformationOptions();
imageTransformationOptions4.setResizeOptions(imageResizeOptions4); imageTransformationOptions4.setResizeOptions(imageResizeOptions4);
// ThumbnailDetails createOptions4 = new ThumbnailDetails();
NodeRef thumbnail4 = this.thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail4 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail4 = thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions4, "half2"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions4, "half2");
return thumbnail4;
}
}, false, true);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(gifOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half2", 1))); checkRenditioned(gifOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half2", 1)));
checkRendition("half2", thumbnail4); checkRendition(gifOrig, "half2", thumbnail4);
outputThumbnailTempContentLocation(thumbnail4, "jpg", "half2 - 50%x50%, from gif"); outputThumbnailTempContentLocation(thumbnail4, "jpg", "half2 - 50%x50%, from gif");
return null;
}
}, false, true);
} }
public void testDuplicationNames() throws Exception public void testDuplicationNames() throws Exception
{ {
checkTransformer(); checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); final NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
setComplete();
endTransaction();
final ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64); imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64); imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true); imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions); imageTransformationOptions.setResizeOptions(imageResizeOptions);
// ThumbnailDetails createOptions = new ThumbnailDetails();
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail1 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
return thumbnail1;
}
}, false, true);
assertNotNull(thumbnail1); assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1))); Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1)));
checkRendition("small", thumbnail1); checkRendition(jpgOrig, "small", thumbnail1);
// the origional thumbnail is returned if we are attempting to create a duplicate // the origional thumbnail is returned if we are attempting to create a duplicate
NodeRef duplicate = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG, final NodeRef duplicate = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG,
imageTransformationOptions, "small"); imageTransformationOptions, "small");
return thumbnail;
}
}, false, true);
assertNotNull(duplicate); assertNotNull(duplicate);
assertEquals(duplicate, thumbnail1); assertEquals(duplicate, thumbnail1);
} }
@@ -588,6 +758,96 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(ContentModel.TYPE_THUMBNAIL, secureNodeService.getType(thumbnail1)); assertEquals(ContentModel.TYPE_THUMBNAIL, secureNodeService.getType(thumbnail1));
} }
private boolean isEqual(InputStream i1, InputStream i2) throws IOException
{
ReadableByteChannel ch1 = Channels.newChannel(i1);
ReadableByteChannel ch2 = Channels.newChannel(i2);
ByteBuffer buf1 = ByteBuffer.allocateDirect(1024);
ByteBuffer buf2 = ByteBuffer.allocateDirect(1024);
try {
while (true) {
int n1 = ch1.read(buf1);
int n2 = ch2.read(buf2);
if (n1 == -1 || n2 == -1) return n1 == n2;
buf1.flip();
buf2.flip();
for (int i = 0; i < Math.min(n1, n2); i++)
if (buf1.get() != buf2.get())
return false;
buf1.compact();
buf2.compact();
}
} finally {
if (i1 != null) i1.close();
if (i2 != null) i2.close();
}
}
/**
* Test that multiple updates to source content generate different thumbnails.
*
* @throws Exception
*/
public void testMultipleThumbnailUpdates() throws Exception
{
checkTransformer();
NodeRef content = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_JPEG,
contentService.getReader(content, ContentModel.PROP_CONTENT).getMimetype());
// Create a thumbnail
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
NodeRef thumbnail = this.thumbnailService.createThumbnail(content, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
// Thumbnails should always be of type cm:thumbnail.
assertEquals(ContentModel.TYPE_THUMBNAIL, secureNodeService.getType(thumbnail));
// make a copy of the thumbnail content
ContentReader thumbnailReader = contentService.getReader(thumbnail, ContentModel.PROP_CONTENT);
InputStream thumbnailStream = thumbnailReader.getContentInputStream();
File file = TempFileProvider.createTempFile(getClass().getName(), "jpg");
OutputStream out = new FileOutputStream(file);
IOUtils.copy(thumbnailStream, out);
InputStream oldThumbnailStream = new FileInputStream(file);
// update the source node content
file = AbstractContentTransformerTest.loadNamedQuickTestFile("quickGEO.jpg");
ContentWriter writer = this.contentService.getWriter(content, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_JPEG);
writer.setEncoding("UTF-8");
writer.putContent(file);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_JPEG,
contentService.getReader(content, ContentModel.PROP_CONTENT).getMimetype());
// update the thumbnail
this.thumbnailService.updateThumbnail(thumbnail, imageTransformationOptions);
// Thumbnails should always be of type cm:thumbnail.
assertEquals(ContentModel.TYPE_THUMBNAIL, secureNodeService.getType(thumbnail));
thumbnailReader = contentService.getReader(thumbnail, ContentModel.PROP_CONTENT);
thumbnailStream = thumbnailReader.getContentInputStream();
// the thumbnail content should be different
assertFalse(isEqual(oldThumbnailStream, thumbnailStream));
}
public void testGetThumbnailByName() throws Exception public void testGetThumbnailByName() throws Exception
{ {
checkTransformer(); checkTransformer();
@@ -608,10 +868,13 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG, this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG,
imageTransformationOptions, "small"); imageTransformationOptions, "small");
setComplete();
endTransaction();
// Try and retrieve the thumbnail // Try and retrieve the thumbnail
NodeRef result2 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small"); NodeRef result2 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small");
assertNotNull(result2); assertNotNull(result2);
checkRendition("small", result2); checkRendition(jpgOrig, "small", result2);
// Check for an other thumbnail that doesn't exist // Check for an other thumbnail that doesn't exist
NodeRef result3 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "anotherone"); NodeRef result3 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "anotherone");
@@ -621,14 +884,23 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
private static class ExpectedAssoc private static class ExpectedAssoc
{ {
private QNamePattern assocTypeQName; private QNamePattern assocTypeQName;
private String assocName; private QNamePattern assocName;
private int count; private int count;
public ExpectedAssoc(QNamePattern assocTypeQName, String assocName, int count) public ExpectedAssoc(QNamePattern assocTypeQName, String assocName, int count)
{ {
super(); super();
this.assocName = assocName != null
? QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName) : null;
this.assocTypeQName = assocTypeQName; this.assocTypeQName = assocTypeQName;
this.count = count;
}
public ExpectedAssoc(QNamePattern assocTypeQName, QNamePattern assocName, int count)
{
super();
this.assocName = assocName; this.assocName = assocName;
this.assocTypeQName = assocTypeQName;
this.count = count; this.count = count;
} }
@@ -637,7 +909,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
return assocTypeQName; return assocTypeQName;
} }
public String getAssocName() public QNamePattern getAssocName()
{ {
return assocName; return assocName;
} }
@@ -692,21 +964,21 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
} }
} }
private void checkRenditioned(NodeRef contentNodeRef, List<ExpectedAssoc> expectedAssocs) { private void checkRenditioned(NodeRef contentNodeRef, List<ExpectedAssoc> expectedAssocs)
{
assertTrue("Renditioned aspect should have been applied", assertTrue("Renditioned aspect should have been applied",
this.secureNodeService.hasAspect(contentNodeRef, RenditionModel.ASPECT_RENDITIONED)); this.secureNodeService.hasAspect(contentNodeRef, RenditionModel.ASPECT_RENDITIONED));
for (ExpectedAssoc expectedAssoc : expectedAssocs) { for (ExpectedAssoc expectedAssoc : expectedAssocs)
QNamePattern qNamePattern = expectedAssoc.getAssocName() != null {
? QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, expectedAssoc.getAssocName()) : null;
List<ChildAssociationRef> assocs = this.secureNodeService.getChildAssocs(contentNodeRef, List<ChildAssociationRef> assocs = this.secureNodeService.getChildAssocs(contentNodeRef,
expectedAssoc.getAssocTypeQName(), qNamePattern); expectedAssoc.getAssocTypeQName(), expectedAssoc.getAssocName());
assertNotNull(assocs); assertNotNull(assocs);
assertEquals(expectedAssoc + " association count mismatch", expectedAssoc.getCount(), assocs.size()); assertEquals(expectedAssoc + " association count mismatch", expectedAssoc.getCount(), assocs.size());
} }
} }
private void checkRendition(String thumbnailName, NodeRef thumbnail) private void checkRendition(final NodeRef sourceNode, final String thumbnailName, final NodeRef thumbnail)
{ {
// Check the thumbnail is of the correct type // Check the thumbnail is of the correct type
assertTrue("Thumbnail should have been a rendition", assertTrue("Thumbnail should have been a rendition",
@@ -732,6 +1004,29 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
ContentData thumbnailData = (ContentData) secureNodeService.getProperty(thumbnail, ContentModel.PROP_CONTENT); ContentData thumbnailData = (ContentData) secureNodeService.getProperty(thumbnail, ContentModel.PROP_CONTENT);
assertNotNull("Thumbnail data was null", thumbnailData); assertNotNull("Thumbnail data was null", thumbnailData);
assertTrue("Thumbnail data was empty", thumbnailData.getSize() > 0); assertTrue("Thumbnail data was empty", thumbnailData.getSize() > 0);
if(sourceNode != null)
{
final String renderedContentKey = AbstractRenderingEngine.getRenderedContentKey(sourceNode, versionService);
QName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailName);
final RenditionDefinition renditionDef = renditionService.createRenditionDefinition(renditionName,
ImageRenderingEngine.NAME);
final QName thumbnailNameQName = thumbnailName != null ?
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailName) : null;
logger.debug("checkedRendition: " + sourceNode + " " + thumbnailNameQName);
String contentUrl = transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<String>()
{
public String execute() throws Throwable
{
String contentUrl = (String)attributeService.getAttribute("RENDITIONED_CONTENT", renderedContentKey,
renditionDef.getRenditionName());
return contentUrl;
};
}, false, true);
assertNull("Cached rendition contentUrl was not cleaned up", contentUrl);
}
} }
private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) throws IOException private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) throws IOException
@@ -770,6 +1065,17 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
return node; return node;
} }
private void updateContent(NodeRef node, String mimetype) throws IOException
{
String ext = this.mimetypeMap.getExtension(mimetype);
File file = AbstractContentTransformerTest.loadNamedQuickTestFile("quickGEO.jpg");
ContentWriter writer = this.contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
writer.setMimetype(mimetype);
writer.setEncoding("UTF-8");
writer.putContent(file);
}
private NodeRef createCorruptedContent(NodeRef parentFolder) throws IOException private NodeRef createCorruptedContent(NodeRef parentFolder) throws IOException
{ {
// The below pdf file has been truncated such that it is identifiable as a PDF but otherwise corrupt. // The below pdf file has been truncated such that it is identifiable as a PDF but otherwise corrupt.
@@ -870,32 +1176,73 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
.getChildRef(); .getChildRef();
checkTransformer(); checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); final NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); setComplete();
endTransaction();
final ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64); imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64); imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true); imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); final ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions); imageTransformationOptions.setResizeOptions(imageResizeOptions);
final NodeRef thumbnail1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
// Create thumbnail - same MIME type // Create thumbnail - same MIME type
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg"); MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg");
return thumbnail;
}
}, false, true);
assertNotNull(thumbnail1); assertNotNull(thumbnail1);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallJpeg", 1))); Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallJpeg", 1)));
checkRendition("smallJpeg", thumbnail1); checkRendition(jpgOrig, "smallJpeg", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "jpg", "smallJpeg - 64x64, marked as thumbnail"); outputThumbnailTempContentLocation(thumbnail1, "jpg", "smallJpeg - 64x64, marked as thumbnail");
return null;
}
}, false, true);
// Create thumbnail - different MIME type // Create thumbnail - different MIME type
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail2 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
// Create thumbnail - same MIME type
NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng"); MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng");
assertNotNull(thumbnail1); return thumbnail;
}
}, false, true);
assertNotNull(thumbnail2);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallPng", 1))); Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallPng", 1)));
checkRendition("smallPng", thumbnail1); checkRendition(jpgOrig, "smallPng", thumbnail2);
outputThumbnailTempContentLocation(thumbnail1, "png", "smallPng - 64x64, marked as thumbnail"); outputThumbnailTempContentLocation(thumbnail2, "png", "smallPng - 64x64, marked as thumbnail");
return null;
}
}, false, true);
// Create thumbnail - different content property // Create thumbnail - different content property
// TODO // TODO
@@ -906,8 +1253,18 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
try try
{ {
imageTransformationOptions.setCommandOptions("-noSuchOption"); imageTransformationOptions.setCommandOptions("-noSuchOption");
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
// Create thumbnail - same MIME type
thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallCO"); MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallCO");
return null;
}
}, false, true);
} catch (ContentIOException ciox) } catch (ContentIOException ciox)
{ {
x = ciox; x = ciox;
@@ -919,28 +1276,46 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
// Create thumbnail - different target assoc details // Create thumbnail - different target assoc details
ThumbnailParentAssociationDetails tpad final ThumbnailParentAssociationDetails tpad
= new ThumbnailParentAssociationDetails(otherFolder, = new ThumbnailParentAssociationDetails(otherFolder,
QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "foo"), QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "foo"),
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bar")); QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bar"));
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail4 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "targetDetails", tpad); MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "targetDetails", tpad);
assertNotNull(thumbnail1); assertNotNull(thumbnail4);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "targetDetails", 1))); Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "targetDetails", 1)));
checkRendition("targetDetails", thumbnail1); checkRendition(jpgOrig, "targetDetails", thumbnail4);
outputThumbnailTempContentLocation(thumbnail1, "png", "targetDetails - 64x64, marked as thumbnail"); outputThumbnailTempContentLocation(thumbnail4, "png", "targetDetails - 64x64, marked as thumbnail");
return null;
}
}, false, true);
// Create thumbnail - null thumbnail name // Create thumbnail - null thumbnail name
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, final NodeRef thumbnail5 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, null); MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, null);
assertNotNull(thumbnail1); assertNotNull(thumbnail5);
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, null, 5))); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
checkRendition(null, thumbnail1); {
outputThumbnailTempContentLocation(thumbnail1, "png", "'null' - 64x64, marked as thumbnail"); @Override
public Void execute() throws Throwable
{
// we expected 4 rendition associations
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, (QNamePattern)null, 4)));
checkRendition(null, null, thumbnail5);
outputThumbnailTempContentLocation(thumbnail5, "png", "'null' - 64x64, marked as thumbnail");
return null;
}
}, false, true);
} }
public void testRegistry() public void testRegistry()
@@ -1005,9 +1380,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(standardMediumIcon, mediumIcon); assertEquals(standardMediumIcon, mediumIcon);
} }
protected void performLongRunningThumbnailTest(final List<ExpectedThumbnail> expectedThumbnails, protected void performLongRunningThumbnailTest(final List<ExpectedThumbnail> expectedThumbnails, final List<ExpectedAssoc> expectedAssocs,
final List<ExpectedAssoc> expectedAssocs, final LongRunningConcurrentWork concurrentWork, final LongRunningConcurrentWork concurrentWork, final Integer retryPeriod, final Integer quietPeriod) throws Exception
final Integer retryPeriod, final Integer quietPeriod) throws Exception
{ {
long saveRetryPeriod = failureHandlingOptions.getRetryPeriod(); long saveRetryPeriod = failureHandlingOptions.getRetryPeriod();
long saveQuietPeriod = failureHandlingOptions.getQuietPeriod(); long saveQuietPeriod = failureHandlingOptions.getQuietPeriod();
@@ -1041,7 +1415,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
} }
// Create our thumbnail(s) // Create our thumbnail(s)
for (ExpectedThumbnail expectedThumbnail : expectedThumbnails) { for (ExpectedThumbnail expectedThumbnail : expectedThumbnails)
{
ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry() ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry()
.getThumbnailDefinition(expectedThumbnail.getThumbnailName()); .getThumbnailDefinition(expectedThumbnail.getThumbnailName());
@@ -1056,13 +1431,15 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
// Thumbnailing process(es) are running in other threads, do the // Thumbnailing process(es) are running in other threads, do the
// concurrent work here // concurrent work here
if (concurrentWork != null) { if (concurrentWork != null)
{
logger.debug("Starting concurrent work for " + source); logger.debug("Starting concurrent work for " + source);
concurrentWork.run(source); concurrentWork.run(source);
} }
// Verify our concurrent work ran successfully // Verify our concurrent work ran successfully
if (concurrentWork != null) { if (concurrentWork != null)
{
logger.debug("Verifying concurrent work for " + source); logger.debug("Verifying concurrent work for " + source);
concurrentWork.verify(source); concurrentWork.verify(source);
} }
@@ -1071,17 +1448,21 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
// Wait for thumbnail(s) to finish // Wait for thumbnail(s) to finish
long endTime = (new Date()).getTime(); long endTime = (new Date()).getTime();
for (final ExpectedThumbnail expectedThumbnail : expectedThumbnails) { for (final ExpectedThumbnail expectedThumbnail : expectedThumbnails)
{
NodeRef thumbnail = null; NodeRef thumbnail = null;
while ((endTime - startTime) < (TEST_LONG_RUNNING_TRANSFORM_TIME * numIterations)) { while ((endTime - startTime) < (TEST_LONG_RUNNING_TRANSFORM_TIME * numIterations))
{
thumbnail = transactionService.getRetryingTransactionHelper() thumbnail = transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>() { .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
public NodeRef execute() throws Throwable { public NodeRef execute() throws Throwable {
return thumbnailService.getThumbnailByName(source, ContentModel.PROP_CONTENT, return thumbnailService.getThumbnailByName(source, ContentModel.PROP_CONTENT,
expectedThumbnail.getThumbnailName()); expectedThumbnail.getThumbnailName());
} }
}, false, true); }, false, true);
if (thumbnail == null) { if (thumbnail == null)
{
Thread.sleep(200); Thread.sleep(200);
logger.debug("Elapsed " + (endTime - startTime) + " ms of " logger.debug("Elapsed " + (endTime - startTime) + " ms of "
+ TEST_LONG_RUNNING_TRANSFORM_TIME * numIterations + " ms waiting for " + TEST_LONG_RUNNING_TRANSFORM_TIME * numIterations + " ms waiting for "
@@ -1096,14 +1477,17 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
} }
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>() { .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
public Void execute() throws Throwable { {
public Void execute() throws Throwable
{
// Verify that the thumbnail(s) was/were created // Verify that the thumbnail(s) was/were created
for (final ExpectedThumbnail expectedThumbnail : expectedThumbnails) { for (final ExpectedThumbnail expectedThumbnail : expectedThumbnails)
{
String thumbnailName = expectedThumbnail.getThumbnailName(); String thumbnailName = expectedThumbnail.getThumbnailName();
NodeRef thumbnailNodeRef = thumbnailService.getThumbnailByName(source, NodeRef thumbnailNodeRef = thumbnailService.getThumbnailByName(source,
ContentModel.PROP_CONTENT, thumbnailName); ContentModel.PROP_CONTENT, thumbnailName);
checkRendition(thumbnailName, thumbnailNodeRef); checkRendition(source, thumbnailName, thumbnailNodeRef);
} }
// verify associations // verify associations
@@ -1112,6 +1496,9 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
return null; return null;
}; };
}); });
// we expect the transformer to run once for each thumbnail
assertEquals(expectedThumbnails.size(), transformer.getTransformCount());
} }
finally finally
{ {
@@ -1129,8 +1516,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
public void testLongRunningThumbnails() throws Exception public void testLongRunningThumbnails() throws Exception
{ {
logger.debug("Starting testLongRunningThumbnails"); logger.debug("Starting testLongRunningThumbnails");
performLongRunningThumbnailTest( performLongRunningThumbnailTest(Collections.singletonList(ExpectedThumbnail.withName("imgpreview")),
Collections.singletonList(ExpectedThumbnail.withName("imgpreview")),
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "imgpreview", 1)), Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "imgpreview", 1)),
new EmptyLongRunningConcurrentWork(), 60, null); new EmptyLongRunningConcurrentWork(), 60, null);
} }
@@ -1166,22 +1552,18 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
} }
}; };
// we expect an imgpreview thumbnail association but no failed thumbnail association
List<ExpectedAssoc> expectedAssocs = new ArrayList<>(2);
expectedAssocs.add(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "imgpreview", 1));
expectedAssocs.add(new ExpectedAssoc(ContentModel.ASSOC_FAILED_THUMBNAIL, RegexQNamePattern.MATCH_ALL, 0));
performLongRunningThumbnailTest( performLongRunningThumbnailTest(
Collections.singletonList(ExpectedThumbnail.withName("imgpreview")), Collections.singletonList(ExpectedThumbnail.withName("imgpreview")), expectedAssocs, updatePropertyWork, 1, null);
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "imgpreview", 1)),
updatePropertyWork, 1, null);
} }
/** /**
* Verifies that multiple thumbnails can be successfully created. * Verifies that multiple thumbnails can be successfully created.
* *
* Note: the current architecture of the thumbnail and rendition services can't guarantee that there
* won't be failed thumbnails for the scenario covered by this test. In particular, given long-running
* thumbnails/renditions, the concurrent creation of more than one thumbnail may fail with a primary key constraint
* exception because both transactions try to add the same aspect to the same parent content node. Whilst
* the retrying transaction handler correctly handles this scenario, the {@link org.alfresco.service.cmr.action.ActionService}
* incorrectly generates a compensating action (failed thumbnail) when in fact the thumbnail creation is recoverable.
*
* @throws Exception * @throws Exception
*/ */
public void testCreateMultipleLongRunningThumbnails() throws Exception public void testCreateMultipleLongRunningThumbnails() throws Exception
@@ -1198,7 +1580,6 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
List<ExpectedAssoc> expectedAssocs = new ArrayList<>(5); List<ExpectedAssoc> expectedAssocs = new ArrayList<>(5);
expectedAssocs.add(new ExpectedAssoc(RenditionModel.ASSOC_RENDITION, "imgpreview", 1)); expectedAssocs.add(new ExpectedAssoc(RenditionModel.ASSOC_RENDITION, "imgpreview", 1));
expectedAssocs.add(new ExpectedAssoc(RenditionModel.ASSOC_RENDITION, "avatar", 1)); expectedAssocs.add(new ExpectedAssoc(RenditionModel.ASSOC_RENDITION, "avatar", 1));
// expectedAssocs.add(new ExpectedAssoc(ContentModel.ASSOC_FAILED_THUMBNAIL, null, 1));
performLongRunningThumbnailTest(expectedThumbnails, expectedAssocs, new EmptyLongRunningConcurrentWork(), 1, 1); performLongRunningThumbnailTest(expectedThumbnails, expectedAssocs, new EmptyLongRunningConcurrentWork(), 1, 1);
} }