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

@@ -25,60 +25,69 @@
*/
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_IS_COMPONENT_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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.UnimportantTransformException;
import org.alfresco.repo.nodelocator.NodeLocator;
import org.alfresco.repo.nodelocator.SelfNodeLocator;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.rendition.RenderingEngineDefinitionImpl;
import org.alfresco.repo.rendition.RenditionDefinitionImpl;
import org.alfresco.repo.rendition.RenditionLocation;
import org.alfresco.repo.rendition.RenditionLocationResolver;
import org.alfresco.repo.rendition.RenditionNodeManager;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.SerializedTransformationOptionsAccessor;
import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
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_ORPHAN_EXISTING_RENDITION;
import static org.alfresco.service.cmr.rendition.RenditionService.PARAM_RENDITION_NODETYPE;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.UnimportantTransformException;
import org.alfresco.repo.nodelocator.NodeLocator;
import org.alfresco.repo.nodelocator.SelfNodeLocator;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.rendition.RenderingEngineDefinitionImpl;
import org.alfresco.repo.rendition.RenditionDefinitionImpl;
import org.alfresco.repo.rendition.RenditionLocation;
import org.alfresco.repo.rendition.RenditionLocationResolver;
import org.alfresco.repo.rendition.RenditionNodeManager;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.SerializedTransformationOptionsAccessor;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionService;
import org.alfresco.service.namespace.NamespaceException;
import org.alfresco.service.namespace.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;
/**
@@ -89,11 +98,13 @@ import com.sun.star.lang.NullPointerException;
* @author Nick Smith
* @since 3.3
*/
public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase implements TransactionListener
{
/** Logger */
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 DEFAULT_RUN_AS_NAME = AuthenticationUtil.getSystemUserName();
@@ -144,7 +155,10 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
/* Injected Services */
protected ContentService contentService;
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 */
/**
@@ -215,7 +229,22 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
private final NodeLocator temporaryParentNodeLocator;
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.
*
@@ -526,11 +555,13 @@ public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase
RenderingContext context = new RenderingContext(sourceNode,
renditionDefinition,
targetContentProp);
render(context);
targetContentProp);
render(context);
// This is a workaround for the fact that actions don't have return
// 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;
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)
{
// 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");
}
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;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.TransformerConfig;
import org.alfresco.repo.content.transform.TransformerDebug;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.TransformerConfig;
import org.alfresco.repo.content.transform.TransformerDebug;
import org.alfresco.repo.content.transform.UnsupportedTransformationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionDetails;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenditionCancelledException;
import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.TransformationOptionLimits;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions.TransformationSourceOptionsSerializer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.action.ActionServiceException;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionDetails;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.RenditionCancelledException;
import org.alfresco.service.cmr.rendition.RenditionServiceException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NoTransformerException;
import org.alfresco.service.cmr.repository.TransformationOptionLimits;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions;
import org.alfresco.service.cmr.repository.TransformationSourceOptions.TransformationSourceOptionsSerializer;
import org.alfresco.util.TempFileProvider;
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
*/
public abstract class AbstractTransformationRenderingEngine extends AbstractRenderingEngine
public abstract class AbstractTransformationRenderingEngine extends AbstractRenderingEngine implements ApplicationContextAware
{
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.
*/
public static final String PARAM_USE = TransformerConfig.USE.replaceAll("\\.", "");
private ApplicationContext applicationContext;
/* 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";
@@ -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 Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers;
private ContentStore tempStore;
public Collection<TransformationSourceOptionsSerializer> getSourceOptionsSerializers()
{
return sourceOptionsSerializers;
}
public void setApplicationContext(ApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
}
public void setSourceOptionsSerializers(Collection<TransformationSourceOptionsSerializer> sourceOptionsSerializers)
{
@@ -157,166 +173,198 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
if (executorService == null)
{
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)
* @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext)
*/
@Override
protected void render(RenderingContext 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
{
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())
{
// check for existing transformed content and reuse if present to avoid re-rendering
String existingTransformedContentUrl = context.getExistingTransformedContentUrl();
if(existingTransformedContentUrl != null)
{
if(logger.isDebugEnabled())
{
logger.info("Cancelling of multiple concurrent action instances " +
"currently unsupported, this action can't be cancelled");
logger.debug("Reusing existing rendered content " + existingTransformedContentUrl
+ " for rendering context " + context);
}
}
// 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)
{
ContentReader existingTransformedContent = tempStore.getReader(existingTransformedContentUrl);
copyTransformedContent(existingTransformedContent, context);
}
else
{
if(logger.isDebugEnabled())
{
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
{
Thread.sleep(CANCELLED_ACTION_POLLING_INTERVAL);
if (transformTask.isDone())
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())
{
actionCompleted = true;
break;
logger.info("Cancelling of multiple concurrent action instances " +
"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
if (logger.isDebugEnabled())
Thread.sleep(CANCELLED_ACTION_POLLING_INTERVAL);
if (transformTask.isDone())
{
logger.debug("Transformation did not obey timeout limit, " +
"rendition action is moving on");
actionCompleted = true;
break;
}
break;
}
if (executionSummary != null)
{
ExecutionDetails executionDetails =
actionTrackingService.getExecutionDetails(executionSummary);
if (executionDetails != null)
// Check timeout in case transformer doesn't obey it
if (options.getTimeoutMs() > 0 &&
new Date().getTime() - startTime > (options.getTimeoutMs() + CANCELLED_ACTION_POLLING_INTERVAL))
{
actionCancelled = executionDetails.isCancelRequested();
if (actionCancelled)
// We hit a timeout, let the transform thread continue but results will be ignored
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)
{
// 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)
catch (InterruptedException e)
{
// We should never be in this state, but just in case
throw new RenditionServiceException("Target of transformation not present");
// entire thread was asked to stop
actionCancelled = true;
transformTask.cancel(true);
break;
}
writer.putContent(tempTarget.getReader().getContentInputStream());
}
catch (ExecutionException e)
if (actionCancelled)
{
// 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());
throw new RenditionCancelledException("Rendition action cancelled");
}
catch (InterruptedException e)
if (!actionCompleted && !actionCancelled)
{
// We were asked to stop
transformTask.cancel(true);
}
}
throw new RenditionServiceException("Transformation failed to obey timeout limit");
}
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);
@@ -404,7 +452,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
return paramList;
}
/**
* Implementation of <code>Callable</code> for doing the work of the transformation
* which returns the temporary content writer if successful.
@@ -440,7 +488,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend
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
ContentWriter tempContentWriter = contentService.getTempWriter();
ContentWriter tempContentWriter = tempStore.getWriter(ContentContext.NULL_CONTEXT);
tempContentWriter.setMimetype(targetMimeType);
try
{

View File

@@ -265,8 +265,8 @@ public class ThumbnailServiceImpl implements ThumbnailService,
if (logger.isDebugEnabled())
{
logger.debug("Thumbnail created " + childAssoc + " for sourceNodeRef " + sourceNodeRef + ", thumbnail " + thumbnailName
+ ", thumbnailNodeRef " + thumbnailNodeRef);
logger.debug("Thumbnail created " + childAssoc + " for sourceNodeRef " + sourceNodeRef + ", thumbnail "
+ thumbnailName + ", thumbnailNodeRef " + thumbnailNodeRef);
}
// 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
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>()
{
@@ -392,18 +398,12 @@ public class ThumbnailServiceImpl implements ThumbnailService,
{
public NodeRef doWork() throws Exception
{
return createThumbnailNode( node,
contentProperty,
mimetype,
transformationOptions,
thumbnailName,
assocDetails);
return createThumbnailNode(node, definition, thumbnailName);
}
}, AuthenticationUtil.getSystemUserName());
}
}, false, requiresNew);
}
private QName getThumbnailQName(String localThumbnailName)
@@ -717,14 +717,8 @@ public class ThumbnailServiceImpl implements ThumbnailService,
return definition;
}
private NodeRef createThumbnailNode(final NodeRef node, final QName contentProperty,
final String mimetype, final TransformationOptions transformationOptions, final String thumbnailName,
final ThumbnailParentAssociationDetails assocDetails)
private NodeRef createThumbnailNode(final NodeRef node, final RenditionDefinition definition, final String thumbnailName)
{
// Get the name of the thumbnail and add to properties map
QName thumbnailQName = getThumbnailQName(thumbnailName);
RenditionDefinition definition = createRenditionDefinition(contentProperty, mimetype,
transformationOptions, thumbnailQName, assocDetails);
try
{
ChildAssociationRef thumbnailAssoc = renditionService.render(node, definition);