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

@@ -25,60 +25,69 @@
*/ */
package org.alfresco.repo.rendition.executer; package org.alfresco.repo.rendition.executer;
import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_DESTINATION_PATH_TEMPLATE; import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_DESTINATION_PATH_TEMPLATE;
import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_IS_COMPONENT_RENDITION; import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_IS_COMPONENT_RENDITION;
import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_ORPHAN_EXISTING_RENDITION; import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_ORPHAN_EXISTING_RENDITION;
import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_RENDITION_NODETYPE; import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_RENDITION_NODETYPE;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; 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.model.ContentModel; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.RenditionModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.transform.UnimportantTransformException; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.nodelocator.NodeLocator; import org.alfresco.repo.content.transform.UnimportantTransformException;
import org.alfresco.repo.nodelocator.SelfNodeLocator; import org.alfresco.repo.nodelocator.NodeLocator;
import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.nodelocator.SelfNodeLocator;
import org.alfresco.repo.rendition.RenderingEngineDefinitionImpl; import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.rendition.RenditionDefinitionImpl; import org.alfresco.repo.rendition.RenderingEngineDefinitionImpl;
import org.alfresco.repo.rendition.RenditionLocation; import org.alfresco.repo.rendition.RenditionDefinitionImpl;
import org.alfresco.repo.rendition.RenditionLocationResolver; import org.alfresco.repo.rendition.RenditionLocation;
import org.alfresco.repo.rendition.RenditionNodeManager; import org.alfresco.repo.rendition.RenditionLocationResolver;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.rendition.RenditionNodeManager;
import org.alfresco.service.cmr.action.Action; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ExecutionSummary; import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.rendition.RenderCallback; import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.rendition.RenditionServiceException; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.SerializedTransformationOptionsAccessor; import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.namespace.NamespaceException; import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.util.GUID; import org.alfresco.service.cmr.repository.SerializedTransformationOptionsAccessor;
import org.apache.commons.logging.Log; import org.alfresco.service.cmr.version.Version;
import org.apache.commons.logging.LogFactory; import org.alfresco.service.cmr.version.VersionService;
import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
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.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
import com.sun.star.lang.NullPointerException; import com.sun.star.lang.NullPointerException;
/** /**
@@ -89,11 +98,13 @@ 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();
@@ -144,7 +155,10 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
/* Injected Services */ /* Injected Services */
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 */
/** /**
@@ -215,7 +229,22 @@ 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.
* *
@@ -526,11 +555,13 @@ 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());
} }
/** /**
@@ -759,111 +790,220 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
result = defaultValue; result = defaultValue;
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
{
private final String guid = GUID.generate();
private final NodeRef sourceNode;
private final RenditionDefinition definition;
private final QName renditionContentProperty;
private String renderedContentKey;
private ChildAssociationRef caNodeRef;
/**
* @param sourceNode NodeRef
* @param definition RenditionDefinition
* @param renditionContentProperty QName
*/
RenderingContext(NodeRef sourceNode, RenditionDefinition definition, QName renditionContentProperty)
{
this.sourceNode = sourceNode;
this.definition = definition;
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;
}
/**
* @return the sourceNode
*/
public NodeRef getSourceNode()
{
return this.sourceNode;
}
/**
* Lazily instantiation of the ChildAssociationRef
* @return ChildAssociationRef
*/
public synchronized ChildAssociationRef getChildAssociationRef()
{
if (this.caNodeRef == null)
{
this.caNodeRef = createRenditionNodeAssoc(sourceNode, definition);
}
return this.caNodeRef;
}
/**
* @return the destinationNode
*/
public NodeRef getDestinationNode()
{
return getChildAssociationRef().getChildRef();
}
/**
* @return the definition
*/
public RenditionDefinition getDefinition()
{
return this.definition;
}
public <T> T getCheckedParam(String paramName, Class<T> clazz)
{
return AbstractRenderingEngine.getCheckedParam(paramName, clazz, definition);
}
public <T> T getParamWithDefault(String paramName, T defaultValue)
{
return AbstractRenderingEngine.getParamWithDefault(paramName, defaultValue, definition);
}
public ContentReader makeContentReader()
{
QName srcContentProp = getParamWithDefault(PARAM_SOURCE_CONTENT_PROPERTY, DEFAULT_CONTENT_PROPERTY);
ContentReader contentReader = contentService.getReader(sourceNode, srcContentProp);
if (contentReader == null || !contentReader.exists())
{
throw new UnimportantTransformException(CONTENT_READER_NOT_FOUND_MESSAGE);
}
return contentReader;
}
public ContentWriter makeContentWriter()
{
ContentWriter contentWriter = contentService.getWriter(getDestinationNode(), renditionContentProperty, true);
String mimetype = getTargetMimeType(this);
contentWriter.setMimetype(mimetype);
String encoding = getTargetEncoding(this);
contentWriter.setEncoding(encoding);
return contentWriter;
}
public int getIntegerParam(String key, int defaultValue)
{
Serializable serializable = definition.getParameterValue(key);
if (serializable == null)
return defaultValue;
else
{
Number number = (Number) serializable;
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 class RenderingContext implements SerializedTransformationOptionsAccessor
{
private final NodeRef sourceNode;
private final RenditionDefinition definition;
private final QName renditionContentProperty;
private ChildAssociationRef caNodeRef;
/**
* @param sourceNode NodeRef
* @param definition RenditionDefinition
* @param renditionContentProperty QName
*/
public RenderingContext(NodeRef sourceNode,//
RenditionDefinition definition,//
QName renditionContentProperty)
{
this.sourceNode = sourceNode;
this.definition = definition;
this.renditionContentProperty = renditionContentProperty;
}
/**
* @return the sourceNode
*/
public NodeRef getSourceNode()
{
return this.sourceNode;
}
/**
* Lazily instantiation of the ChildAssociationRef
* @return ChildAssociationRef
*/
public synchronized ChildAssociationRef getChildAssociationRef()
{
if (this.caNodeRef == null)
{
this.caNodeRef = createRenditionNodeAssoc(sourceNode, definition);
}
return this.caNodeRef;
}
/**
* @return the destinationNode
*/
public NodeRef getDestinationNode()
{
return getChildAssociationRef().getChildRef();
}
/**
* @return the definition
*/
public RenditionDefinition getDefinition()
{
return this.definition;
}
public <T> T getCheckedParam(String paramName, Class<T> clazz)
{
return AbstractRenderingEngine.getCheckedParam(paramName, clazz, definition);
}
public <T> T getParamWithDefault(String paramName, T defaultValue)
{
return AbstractRenderingEngine.getParamWithDefault(paramName, defaultValue, definition);
}
public ContentReader makeContentReader()
{
QName srcContentProp = getParamWithDefault(PARAM_SOURCE_CONTENT_PROPERTY, DEFAULT_CONTENT_PROPERTY);
ContentReader contentReader = contentService.getReader(sourceNode, srcContentProp);
if (contentReader == null || !contentReader.exists())
{
throw new UnimportantTransformException(CONTENT_READER_NOT_FOUND_MESSAGE);
}
return contentReader;
}
public ContentWriter makeContentWriter()
{
ContentWriter contentWriter = contentService.getWriter(getDestinationNode(), renditionContentProperty, true);
String mimetype = getTargetMimeType(this);
contentWriter.setMimetype(mimetype);
String encoding = getTargetEncoding(this);
contentWriter.setEncoding(encoding);
return contentWriter;
}
public int getIntegerParam(String key, int defaultValue)
{
Serializable serializable = definition.getParameterValue(key);
if (serializable == null)
return defaultValue;
else
{
Number number = (Number) serializable;
return number.intValue();
}
}
}
protected void tagSourceNodeAsRenditioned(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef) protected void tagSourceNodeAsRenditioned(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef)
{ {
// Adds the 'Renditioned' aspect to the source node if it // Adds the 'Renditioned' aspect to the source node if it
@@ -1120,5 +1260,69 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
"multiple instances of the same action"); "multiple instances of the same action");
} }
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

@@ -26,43 +26,50 @@
package org.alfresco.repo.rendition.executer; package org.alfresco.repo.rendition.executer;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; 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.transform.ContentTransformer; import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.transform.TransformerConfig; import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.TransformerConfig;
import org.alfresco.repo.content.transform.TransformerDebug;
import org.alfresco.repo.content.transform.UnsupportedTransformationException; import org.alfresco.repo.content.transform.UnsupportedTransformationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
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.ExecutionDetails; import org.alfresco.service.cmr.action.ExecutionDetails;
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.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenditionCancelledException; import org.alfresco.service.cmr.rendition.RenditionCancelledException;
import org.alfresco.service.cmr.rendition.RenditionServiceException; import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.TransformationOptionLimits; 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.apache.commons.logging.Log; import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log;
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);
@@ -112,6 +119,8 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
* This optional {@link String} parameter specifies the type (or use) of the rendition. * This optional {@link String} parameter specifies the type (or use) of the rendition.
*/ */
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";
@@ -119,11 +128,18 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
private static final String TRANSFORMING_ERROR_MESSAGE = "Some error occurred during document transforming. Error message: "; private static final String TRANSFORMING_ERROR_MESSAGE = "Some error occurred during document transforming. Error message: ";
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)
{ {
@@ -157,166 +173,198 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
if (executorService == null) if (executorService == null)
{ {
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());
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext) * @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext)
*/ */
@Override @Override
protected void render(RenderingContext context) protected void render(RenderingContext context)
{ {
ContentReader contentReader = context.makeContentReader(); // check for existing transformed content and reuse if present to avoid re-rendering
// There will have been an exception if there is no content data so contentReader is not null. String existingTransformedContentUrl = context.getExistingTransformedContentUrl();
String sourceUrl = contentReader.getContentUrl(); if(existingTransformedContentUrl != null)
String sourceMimeType = contentReader.getMimetype(); {
String targetMimeType = getTargetMimeType(context); if(logger.isDebugEnabled())
// The child NodeRef gets created here
TransformationOptions options = getTransformOptions(context);
// Log the following getTransform() as trace so we can see the wood for the trees
ContentTransformer transformer;
boolean orig = TransformerDebug.setDebugOutput(false);
try
{
transformer = this.contentService.getTransformer(sourceUrl, sourceMimeType, contentReader.getSize(), targetMimeType, options);
}
finally
{
TransformerDebug.setDebugOutput(orig);
}
if (null == transformer)
{
// There's no transformer available for the requested rendition!
throw new RenditionServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, sourceMimeType,
targetMimeType));
}
if (!transformer.isTransformable(sourceMimeType, contentReader.getSize(), targetMimeType, options))
{
throw new RenditionServiceException(String.format(NOT_TRANSFORMABLE_MESSAGE_PATTERN, sourceMimeType, targetMimeType));
}
long startTime = new Date().getTime();
boolean actionCancelled = false;
boolean actionCompleted = false;
// Cache the execution summary to get details later
ExecutionSummary executionSummary = null;
try
{
executionSummary = getExecutionSummary(context);
}
catch (ActionServiceException e)
{
if (logger.isInfoEnabled())
{ {
logger.info("Cancelling of multiple concurrent action instances " + logger.debug("Reusing existing rendered content " + existingTransformedContentUrl
"currently unsupported, this action can't be cancelled"); + " for rendering context " + context);
} }
}
// Call the transform in a different thread so we can move on if cancelled ContentReader existingTransformedContent = tempStore.getReader(existingTransformedContentUrl);
FutureTask<ContentWriter> transformTask = new FutureTask<ContentWriter>( copyTransformedContent(existingTransformedContent, context);
new TransformationCallable(contentReader, targetMimeType, options, context, }
AuthenticationUtil.getFullyAuthenticatedUser())); else
getExecutorService().execute(transformTask); {
if(logger.isDebugEnabled())
// Start checking for cancellation or timeout {
while (true) logger.debug("Rendering for rendering context " + context);
{ }
ContentReader contentReader = context.makeContentReader();
// There will have been an exception if there is no content data so contentReader is not null.
String sourceUrl = contentReader.getContentUrl();
String sourceMimeType = contentReader.getMimetype();
String targetMimeType = getTargetMimeType(context);
// The child NodeRef gets created here
TransformationOptions options = getTransformOptions(context);
// Log the following getTransform() as trace so we can see the wood for the trees
ContentTransformer transformer;
boolean orig = TransformerDebug.setDebugOutput(false);
try try
{ {
Thread.sleep(CANCELLED_ACTION_POLLING_INTERVAL); transformer = this.contentService.getTransformer(sourceUrl, sourceMimeType, contentReader.getSize(), targetMimeType, options);
if (transformTask.isDone()) }
finally
{
TransformerDebug.setDebugOutput(orig);
}
if (null == transformer)
{
// There's no transformer available for the requested rendition!
throw new RenditionServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, sourceMimeType,
targetMimeType));
}
if (!transformer.isTransformable(sourceMimeType, contentReader.getSize(), targetMimeType, options))
{
throw new RenditionServiceException(String.format(NOT_TRANSFORMABLE_MESSAGE_PATTERN, sourceMimeType, targetMimeType));
}
long startTime = new Date().getTime();
boolean actionCancelled = false;
boolean actionCompleted = false;
// Cache the execution summary to get details later
ExecutionSummary executionSummary = null;
try
{
executionSummary = getExecutionSummary(context);
}
catch (ActionServiceException e)
{
if (logger.isInfoEnabled())
{ {
actionCompleted = true; logger.info("Cancelling of multiple concurrent action instances " +
break; "currently unsupported, this action can't be cancelled");
} }
// Check timeout in case transformer doesn't obey it }
if (options.getTimeoutMs() > 0 &&
new Date().getTime() - startTime > (options.getTimeoutMs() + CANCELLED_ACTION_POLLING_INTERVAL)) // Call the transform in a different thread so we can move on if cancelled
FutureTask<ContentWriter> transformTask = new FutureTask<ContentWriter>(
new TransformationCallable(contentReader, targetMimeType, options, context,
AuthenticationUtil.getFullyAuthenticatedUser()));
getExecutorService().execute(transformTask);
// Start checking for cancellation or timeout
while (true)
{
try
{ {
// We hit a timeout, let the transform thread continue but results will be ignored Thread.sleep(CANCELLED_ACTION_POLLING_INTERVAL);
if (logger.isDebugEnabled()) if (transformTask.isDone())
{ {
logger.debug("Transformation did not obey timeout limit, " + actionCompleted = true;
"rendition action is moving on"); break;
} }
break; // Check timeout in case transformer doesn't obey it
} if (options.getTimeoutMs() > 0 &&
if (executionSummary != null) new Date().getTime() - startTime > (options.getTimeoutMs() + CANCELLED_ACTION_POLLING_INTERVAL))
{
ExecutionDetails executionDetails =
actionTrackingService.getExecutionDetails(executionSummary);
if (executionDetails != null)
{ {
actionCancelled = executionDetails.isCancelRequested(); // We hit a timeout, let the transform thread continue but results will be ignored
if (actionCancelled) if (logger.isDebugEnabled())
{ {
if (logger.isDebugEnabled()) logger.debug("Transformation did not obey timeout limit, " +
"rendition action is moving on");
}
break;
}
if (executionSummary != null)
{
ExecutionDetails executionDetails =
actionTrackingService.getExecutionDetails(executionSummary);
if (executionDetails != null)
{
actionCancelled = executionDetails.isCancelRequested();
if (actionCancelled)
{ {
logger.debug("Cancelling transformation"); if (logger.isDebugEnabled())
{
logger.debug("Cancelling transformation");
}
transformTask.cancel(true);
break;
} }
transformTask.cancel(true);
break;
} }
} }
} }
} catch (InterruptedException e)
catch (InterruptedException e)
{
// entire thread was asked to stop
actionCancelled = true;
transformTask.cancel(true);
break;
}
}
if (actionCancelled)
{
throw new RenditionCancelledException("Rendition action cancelled");
}
if (!actionCompleted && !actionCancelled)
{
throw new RenditionServiceException("Transformation failed to obey timeout limit");
}
if (actionCompleted)
{
// Copy content from temp writer to real writer
ContentWriter writer = context.makeContentWriter();
try
{
// We should not need another timeout here, things should be ready for us
ContentWriter tempTarget = transformTask.get();
if (tempTarget == null)
{ {
// We should never be in this state, but just in case // entire thread was asked to stop
throw new RenditionServiceException("Target of transformation not present"); actionCancelled = true;
transformTask.cancel(true);
break;
} }
writer.putContent(tempTarget.getReader().getContentInputStream());
} }
catch (ExecutionException e)
if (actionCancelled)
{ {
// Unwrap our cause and throw that throw new RenditionCancelledException("Rendition action cancelled");
Throwable transformException = e.getCause();
if (transformException instanceof RuntimeException)
{
throw (RuntimeException) e.getCause();
}
throw new RenditionServiceException(TRANSFORMING_ERROR_MESSAGE + e.getCause().getMessage(), e.getCause());
} }
catch (InterruptedException e)
if (!actionCompleted && !actionCancelled)
{ {
// We were asked to stop throw new RenditionServiceException("Transformation failed to obey timeout limit");
transformTask.cancel(true); }
}
} if (actionCompleted)
{
try
{
// We should not need another timeout here, things should be ready for us
ContentWriter tempTarget = transformTask.get();
if (tempTarget == null)
{
// We should never be in this state, but just in case
throw new RenditionServiceException("Target of transformation not present");
}
// save transform result in case we need to retry
context.setExistingTransformedContentUrl(tempTarget.getReader().getContentUrl());
copyTransformedContent(tempTarget.getReader(), context);
}
catch (ExecutionException e)
{
// Unwrap our cause and throw that
Throwable transformException = e.getCause();
if (transformException instanceof RuntimeException)
{
throw (RuntimeException) e.getCause();
}
throw new RenditionServiceException(TRANSFORMING_ERROR_MESSAGE + e.getCause().getMessage(), e.getCause());
}
catch (InterruptedException e)
{
// We were asked to stop
transformTask.cancel(true);
}
}
}
} }
protected abstract TransformationOptions getTransformOptions(RenderingContext context); protected abstract TransformationOptions getTransformOptions(RenderingContext context);
@@ -404,7 +452,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
return paramList; return paramList;
} }
/** /**
* Implementation of <code>Callable</code> for doing the work of the transformation * Implementation of <code>Callable</code> for doing the work of the transformation
* which returns the temporary content writer if successful. * which returns the temporary content writer if successful.
@@ -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

@@ -26,73 +26,75 @@
package org.alfresco.repo.rendition; package org.alfresco.repo.rendition;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
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.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; 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; import org.alfresco.repo.action.executer.ExporterActionExecuter;
import org.alfresco.repo.action.executer.ExporterActionExecuter; import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformerRegistry;
import org.alfresco.repo.content.transform.ContentTransformerRegistry; import org.alfresco.repo.content.transform.UnimportantTransformException;
import org.alfresco.repo.content.transform.UnimportantTransformException; 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.model.Repository;
import org.alfresco.repo.model.Repository; import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.rendition.executer.AbstractRenderingEngine;
import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; import org.alfresco.repo.rendition.executer.FreemarkerRenderingEngine;
import org.alfresco.repo.rendition.executer.FreemarkerRenderingEngine; import org.alfresco.repo.rendition.executer.ImageRenderingEngine;
import org.alfresco.repo.rendition.executer.ImageRenderingEngine; import org.alfresco.repo.rendition.executer.ReformatRenderingEngine;
import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; import org.alfresco.repo.security.authentication.AuthenticationUtil;
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;
import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
import org.alfresco.service.cmr.rendition.RenderCallback; import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; import org.alfresco.service.cmr.rendition.RenderingEngineDefinition;
import org.alfresco.service.cmr.rendition.RenditionCancelledException; import org.alfresco.service.cmr.rendition.RenditionCancelledException;
import org.alfresco.service.cmr.rendition.RenditionDefinition; 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.rendition.RenditionServiceException; import org.alfresco.service.cmr.rendition.RenditionServiceException;
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;
import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentIOException;
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;
import org.alfresco.service.cmr.repository.CropSourceOptions.CropSourceOptionsSerializer; import org.alfresco.service.cmr.repository.CropSourceOptions.CropSourceOptionsSerializer;
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.ScriptLocation; 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.namespace.NamespaceService; import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.namespace.QName;
import org.alfresco.test_category.OwnJVMTestsCategory; import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.BaseAlfrescoSpringTest; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.Pair; import org.alfresco.test_category.OwnJVMTestsCategory;
import org.junit.experimental.categories.Category; import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.Pair;
import org.junit.experimental.categories.Category;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
/** /**
@@ -1292,7 +1294,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
assertNull(results.getAssoc()); assertNull(results.getAssoc());
assertEquals("Expected a UnimportantTransformException", UnimportantTransformException.class, results.getThrowable().getClass()); assertEquals("Expected a UnimportantTransformException", UnimportantTransformException.class, results.getThrowable().getClass());
} }
/** /**
* This method performs an asynchronous rendition and calls back the result to the * This method performs an asynchronous rendition and calls back the result to the
@@ -2606,15 +2608,22 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
assertTrue("upper left should be "+topLeft, Integer.toHexString(rgbAtTopLeft).endsWith(topLeft)); assertTrue("upper left should be "+topLeft, Integer.toHexString(rgbAtTopLeft).endsWith(topLeft));
int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1); int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
assertTrue("lower right should be "+bottomRight, Integer.toHexString(rgbAtBottomRight).endsWith(bottomRight)); assertTrue("lower right should be "+bottomRight, Integer.toHexString(rgbAtBottomRight).endsWith(bottomRight));
} }
/** /**
* A dummy rendering engine used in testing * A dummy rendering engine used in testing
*/ */
private static class DummyHelloWorldRenditionEngine extends AbstractRenderingEngine private static class DummyHelloWorldRenditionEngine extends AbstractRenderingEngine
{ {
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);
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");
return thumbnail;
}
}, false, true);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
assertNotNull(thumbnail0); assertNotNull(thumbnail0);
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition("doclib", thumbnail0); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); {
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition(jpgOrig, "doclib", thumbnail0);
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);
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");
return thumbnail;
}
}, false, true);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
assertNotNull(thumbnail0); assertNotNull(thumbnail0);
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition("doclib", thumbnail0); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); {
@Override
public Void execute() throws Throwable
{
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib", 1)));
checkRendition(pdfOrig, "doclib", thumbnail0);
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);
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib_2", 1)));
checkRendition("doclib_2", thumbnail0); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
// Check the length @Override
File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", ".jpg"); public Void execute() throws Throwable
ContentReader reader = this.contentService.getReader(thumbnail0, ContentModel.PROP_CONTENT); {
checkRenditioned(pdfOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "doclib_2", 1)));
long size = reader.getSize(); checkRendition(pdfOrig, "doclib_2", thumbnail0);
System.out.println("size=" + size);
assertTrue("Page 2 should be blank and less than 4500 bytes", size < 4500); // Check the length
File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", ".jpg");
reader.getContent(tempFile); ContentReader reader = contentService.getReader(thumbnail0, ContentModel.PROP_CONTENT);
System.out.println("doclib_2 test: " + tempFile.getPath());
long size = reader.getSize();
System.out.println("size=" + size);
assertTrue("Page 2 should be blank and less than 4500 bytes", size < 4500);
reader.getContent(tempFile);
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>()
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); {
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail1 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
return thumbnail1;
}
}, false, true);
assertNotNull(thumbnail1); assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1)));
checkRendition("small", thumbnail1); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
outputThumbnailTempContentLocation(thumbnail1, "jpg", "small - 64x64, marked as thumbnail"); {
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small", 1)));
checkRendition(jpgOrig, "small", thumbnail1);
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>()
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions2, "small2"); {
checkRenditioned(jpgOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "small2", 1))); @Override
checkRendition("small2", thumbnail2); public NodeRef execute() throws Throwable
outputThumbnailTempContentLocation(thumbnail2, "jpg", "small2 - 64x64, aspect not maintained"); {
NodeRef thumbnail2 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
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)));
checkRendition(jpgOrig, "small2", thumbnail2);
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>()
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions3, "half"); {
checkRenditioned(jpgOrig, @Override
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half", 1))); public NodeRef execute() throws Throwable
checkRendition("half", thumbnail3); {
outputThumbnailTempContentLocation(thumbnail3, "jpg", "half - 50%x50%"); NodeRef thumbnail3 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
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,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half", 1)));
checkRendition(jpgOrig, "half", thumbnail3);
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>()
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions4, "half2"); {
checkRenditioned(gifOrig, Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "half2", 1))); @Override
checkRendition("half2", thumbnail4); public NodeRef execute() throws Throwable
outputThumbnailTempContentLocation(thumbnail4, "jpg", "half2 - 50%x50%, from gif"); {
NodeRef thumbnail4 = thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT,
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)));
checkRendition(gifOrig, "half2", thumbnail4);
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>()
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); {
@Override
public NodeRef execute() throws Throwable
{
NodeRef thumbnail1 = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
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
@@ -769,7 +1064,18 @@ 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,33 +1176,74 @@ 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);
// Create thumbnail - same MIME type final NodeRef thumbnail1 = transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, {
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg"); @Override
public NodeRef execute() throws Throwable
{
// Create thumbnail - same MIME type
NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg");
return thumbnail;
}
}, false, true);
assertNotNull(thumbnail1); assertNotNull(thumbnail1);
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallJpeg", 1))); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
checkRendition("smallJpeg", thumbnail1); {
outputThumbnailTempContentLocation(thumbnail1, "jpg", "smallJpeg - 64x64, marked as thumbnail"); @Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallJpeg", 1)));
checkRendition(jpgOrig, "smallJpeg", thumbnail1);
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>()
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng"); {
assertNotNull(thumbnail1); @Override
checkRenditioned(jpgOrig, public NodeRef execute() throws Throwable
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallPng", 1))); {
checkRendition("smallPng", thumbnail1); // Create thumbnail - same MIME type
outputThumbnailTempContentLocation(thumbnail1, "png", "smallPng - 64x64, marked as thumbnail"); NodeRef thumbnail = thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng");
return thumbnail;
}
}, false, true);
assertNotNull(thumbnail2);
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
@Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "smallPng", 1)));
checkRendition(jpgOrig, "smallPng", thumbnail2);
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,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallCO"); 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");
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);
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "targetDetails", 1))); transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
checkRendition("targetDetails", thumbnail1); {
outputThumbnailTempContentLocation(thumbnail1, "png", "targetDetails - 64x64, marked as thumbnail"); @Override
public Void execute() throws Throwable
{
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, "targetDetails", 1)));
checkRendition(jpgOrig, "targetDetails", thumbnail4);
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,27 +1477,33 @@ 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
checkRenditioned(source, expectedAssocs); checkRenditioned(source, expectedAssocs);
return null; return null;
}; };
}); });
// we expect the transformer to run once for each thumbnail
assertEquals(expectedThumbnails.size(), transformer.getTransformCount());
} }
finally finally
{ {
failureHandlingOptions.setRetryPeriod(saveRetryPeriod); failureHandlingOptions.setRetryPeriod(saveRetryPeriod);
failureHandlingOptions.setQuietPeriod(saveQuietPeriod); failureHandlingOptions.setQuietPeriod(saveQuietPeriod);
} }
} }
@@ -1129,10 +1516,9 @@ 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);
} }