diff --git a/repository/src/main/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java b/repository/src/main/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java index 98c218166c..baf5545d7b 100644 --- a/repository/src/main/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java +++ b/repository/src/main/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java @@ -1,1131 +1,1135 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ -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.rendition2.RenditionService2Impl; -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; - -/** - * This class adds some new behaviour to the standard ActionExecuterAbstractBase - * in order to support the RenditionService. - * - * @author Neil McErlean - * @author Nick Smith - * @since 3.3 - * - * @deprecated The RenditionService is being replace by the simpler async RenditionService2. - */ -@Deprecated -public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase -{ - - /** Logger */ - private static Log logger = LogFactory.getLog(AbstractRenderingEngine.class); - - 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(); - - // A word on the default* fields below: - // - // RenditionExecuters can be executed with or without two optional - // parameters: "rendition node type" - // and a "rendition content property" parameter. - // These parameters can be specified on a per-action basis. - // If no value is specified, then the default is used. - // That default can be injected via Spring. - // If no default is injected via spring, then there is a "default default" - // for the two params - - /** - * This is the default default node type for renditions - used if no value - * is injected from spring. - */ - private static final QName DEFAULT_DEFAULT_RENDITION_NODE_TYPE = ContentModel.TYPE_CONTENT; - - /** - * This is the default default property used to specify where rendition - * content is stored - used if no value is injected from spring. - */ - private static final QName DEFAULT_DEFAULT_RENDITION_CONTENT_PROP = RenditionService2Impl.DEFAULT_RENDITION_CONTENT_PROP; - private static final String DEFAULT_MIMETYPE = RenditionService2Impl.DEFAULT_MIMETYPE; - private static final String DEFAULT_ENCODING = RenditionService2Impl.DEFAULT_ENCODING; - - /** - * This is the default node type that is used when creating rendition - * objects. - */ - private QName defaultRenditionNodeType = DEFAULT_DEFAULT_RENDITION_NODE_TYPE; - - /** - * This is the default property that is used to store rendition objects' - * content. - */ - private QName defaultRenditionContentProp = DEFAULT_DEFAULT_RENDITION_CONTENT_PROP; - - /** - * This is the default content property. - */ - private static final QName DEFAULT_CONTENT_PROPERTY = ContentModel.TYPE_CONTENT; - - /* Injected Services */ - protected ContentService contentService; - protected MimetypeMap mimetypeMap; - protected ActionTrackingService actionTrackingService; - protected NamespaceService namespaceService; - - /* Parameter names common to all Rendering Actions */ - /** - * This optional {@link String} parameter specifies the location of a - * classpath resource which can be used as a placeholder while a rendition - * is being generated. For example, this might be a simple icon to indicate - * a rendition is not yet available. This is intended to be used in - * conjunction with asynchronous generation of renditions. - */ - public static final String PARAM_PLACEHOLDER_RESOURCE_PATH = "placeHolderResourcePath"; - - /** - * This optional {@link QName} parameter specifies which property the - * Rendering Engine uses to read content from the source node in order to - * create a rendition. By default this property will be cm:content. - */ - public static final String PARAM_SOURCE_CONTENT_PROPERTY = "sourceContentProperty"; - - /** - * This optional {@link QName} parameter specifies which property the - * Rendering Engine uses to write content to the rendition node. By default - * the property used is cm:content. - */ - public static final String PARAM_TARGET_CONTENT_PROPERTY = "targetContentProperty"; - - /** - * This optional {@link Boolean} flag property specifies whether a rendition - * should be updated automatically if the source node changes. If set to - * true then the rendition will be re-rendered any time any - * property changes occur on the source node. This parameter defaults to - * false. - */ - public static final String PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE = "update-renditions-on-any-property-change"; - - /** - * This optional {@link String} parameter specifies what user permissions - * are used when creating a rendition. By default the system user is used. - */ - public static final String PARAM_RUN_AS = "runAs"; - - // mime-type is not a common parameter on all Rendering Actions, but it is - // common to many and is used in some common handling code in this class. - /** - * This optional {@link String} parameter specifies the mime type of the - * rendition content. This defaults to the mime type of the source node - * content. - */ - public static final String PARAM_MIME_TYPE = "mime-type"; - - /** - * This optional {@link String} paramter specifies the encoding used to - * create the rendition content. The derfault encoding is UTF-8. - */ - public static final String PARAM_ENCODING = "encoding"; - - /** - * Default {@link NodeLocator} simply returns the source node. - */ - private final static NodeLocator defaultNodeLocator = new SelfNodeLocator(); - - /* - * Injected beans - */ - private RenditionLocationResolver renditionLocationResolver; - protected NodeService nodeService; - private RenditionService renditionService; - private BehaviourFilter behaviourFilter; - - private final NodeLocator temporaryParentNodeLocator; - private final QName temporaryRenditionLinkType; - - /** - * Injects the nodeService bean. - * - * @param nodeService - * the nodeService. - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Injects the renditionService bean. - * - * @param renditionService RenditionService - */ - public void setRenditionService(RenditionService renditionService) - { - this.renditionService = renditionService; - } - - /** - * @param behaviourFilter policy behaviour filter - */ - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - public void setRenditionLocationResolver(RenditionLocationResolver renditionLocationResolver) - { - this.renditionLocationResolver = renditionLocationResolver; - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public AbstractRenderingEngine(NodeLocator temporaryParentNodeLocator, QName temporaryRenditionLinkType) - { - this.publicAction = false; - this.temporaryParentNodeLocator = temporaryParentNodeLocator != null ? temporaryParentNodeLocator - : defaultNodeLocator; - this.temporaryRenditionLinkType = temporaryRenditionLinkType != null ? temporaryRenditionLinkType - : RenditionModel.ASSOC_RENDITION; - } - - public AbstractRenderingEngine() - { - this(null, null); - } - - - - /** - * Sets the default rendition-node type. - * - * @param type String - */ - public void setDefaultRenditionNodeType(String type) - { - QName qname; - try - { - qname = QName.createQName(type); - } - catch (NamespaceException nx) - { - if (logger.isErrorEnabled()) - { - logger.error("Error when setting default rendition node type: ", nx); - } - throw nx; - } - - if (logger.isInfoEnabled()) - { - logger.info("Using default rendition node type: " + qname); - } - this.defaultRenditionNodeType = qname; - } - - /** - * This method returns the type of the default rendition node type. - * - * @return the QName representing the type of the default rendition node - * type. - */ - protected QName getDefaultRenditionNodeType() - { - return defaultRenditionNodeType; - } - - protected String getTargetMimeType(RenderingContext context) - { - return context.getParamWithDefault(PARAM_MIME_TYPE, DEFAULT_MIMETYPE); - } - - protected String getTargetEncoding(RenderingContext context) - { - return context.getParamWithDefault(PARAM_ENCODING, DEFAULT_ENCODING); - } - - /** - * Sets the default rendition content property. - * - * @param prop String - */ - public void setDefaultRenditionContentProp(String prop) - { - QName qname; - try - { - qname = QName.createQName(prop); - } - catch (NamespaceException nx) - { - if (logger.isErrorEnabled()) - { - logger.error("Error when setting default rendition content property: ", nx); - } - throw nx; - } - - if (logger.isInfoEnabled()) - { - logger.info("Using default rendition content property: " + qname); - } - this.defaultRenditionContentProp = qname; - } - - /** - * This method returns the QName of the property that defines the location - * of the rendition content. An example would be cm:content. - * - * @return the QName the property defining the location of the rendition - * content. - */ - protected QName getDefaultRenditionContentProp() - { - return defaultRenditionContentProp; - } - - /** - * Set the content service - * - * @param contentService the content service - */ - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public void setMimetypeMap(MimetypeMap mimetypeMap) - { - this.mimetypeMap = mimetypeMap; - } - - public void setActionTrackingService(ActionTrackingService actionTrackingService) - { - this.actionTrackingService = actionTrackingService; - } - - @Override - protected ActionDefinition createActionDefinition(String definitionName) - { - return new RenderingEngineDefinitionImpl(definitionName); - } - - @Override - protected void executeImpl(final Action action, final NodeRef sourceNode) - { - executeImpl( (RenditionDefinition)action, sourceNode ); - } - - protected void executeImpl(final RenditionDefinition renditionDef, final NodeRef sourceNode) - { - // Don't render the nodes without content. - // MNT-10178 - if (!nodeService.exists(sourceNode)) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Rendition has not been created, because the node no longer exists. (sourceNode=" + sourceNode + ")"); - } - notifyCallbackOfException(renditionDef, new UnimportantTransformException("Rendition was cancelled, because the node no longer exists.")); - return; - } - else if (nodeService.getProperty(sourceNode, ContentModel.PROP_CONTENT) == null) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Rendition has not been created, because the node has no content to render. (sourceNode=" + sourceNode + ")"); - } - notifyCallbackOfException(renditionDef, new UnimportantTransformException("Rendition was cancelled, because the node has no content to render.")); - return; - } - - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Rendering node ").append(sourceNode).append(" with rendition definition ").append( - renditionDef.getRenditionName()); - msg.append("\n").append(" parameters:").append("\n"); - if (renditionDef.getParameterValues().isEmpty() == false) - { - for (String paramKey : renditionDef.getParameterValues().keySet()) - { - msg.append(" ").append(paramKey).append("=").append(renditionDef.getParameterValue(paramKey)).append("\n"); - } - } - else - { - msg.append(" [None]"); - } - logger.debug(msg.toString()); - } - - Serializable runAsParam = renditionDef.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS); - String runAsName = runAsParam == null ? DEFAULT_RUN_AS_NAME : (String) runAsParam; - - // Renditions should all be created by system by default. - // When renditions are created by a user and are to be created under a - // node - // other than the source node, it is possible that the user will not - // have - // permissions to create content under that node. - // For that reason, we execute all rendition actions as system - // by default. - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - @Override - public Void doWork() throws Exception - { - ChildAssociationRef result = null; - try - { - // Check whether this rendition is a component of a larger CompositeRendition - boolean isComponentRendition = isComponentRendition(renditionDef); - if (isComponentRendition == false) - { - // Request that the rendition is initially created - // as a child of the source node - setTemporaryRenditionProperties(sourceNode, renditionDef); - } - - // Have the concrete implementation do the actual rendition - executeRenditionImpl(renditionDef, sourceNode); - - // - if (isComponentRendition == false) - { - // Add renditioned aspect to the source node - tagSourceNodeAsRenditioned(renditionDef, sourceNode); - - // Currently the rendition is on a temporary node, which may - // have the wrong name on it, and for path based renditions is - // in the wrong place - // So, have the correct node created, and switch everything to use it - switchToFinalRenditionNode(renditionDef, sourceNode); - } - - // Grab a link to the rendition node - it's been saved as a parameter for us - // (Wait until now to fetch in case it was moved) - result = (ChildAssociationRef)renditionDef.getParameterValue(PARAM_RESULT); - } catch (Throwable t) - { - notifyCallbackOfException(renditionDef, t); - throwWrappedException(t); - } - if (result != null) - { - notifyCallbackOfResult(renditionDef, result); - } - return null; - } - }, runAsName); - } - - /** - * Is this a standalone rendition, or is it a sub-component of - * a composite rendition? - * This is false for standalone renditions, AND ALSO false for - * the main part of a composite rendition. - * This only returns true if we're currently processing a - * component of a composite rendition. - * @param action Action - * @return boolean - */ - private boolean isComponentRendition(Action action) { - Serializable s = action.getParameterValue(PARAM_IS_COMPONENT_RENDITION); - boolean result = s == null ? false : (Boolean)s; - return result; - } - - protected void executeRenditionImpl(Action action, NodeRef sourceNode) - { - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Executing rendering engine; name:") - .append(this.name).append(", class:") - .append(this.getClass().getName()); - logger.debug(msg.toString()); - } - - checkParameterValues(action); - RenditionDefinition renditionDefinition = checkActionIsRenditionDefinition(action); - checkSourceNodeExists(sourceNode); - - QName targetContentProp = getRenditionContentProperty(renditionDefinition); - - RenderingContext context = new RenderingContext(sourceNode, - renditionDefinition, - targetContentProp); - render(context); - // This is a workaround for the fact that actions don't have return - // values. - action.getParameterValues().put(PARAM_RESULT, context.getChildAssociationRef()); - } - - /** - * This method can be overridden by subclasses to provide checking of parameter - * values. - * If a parameter value is illegal or inappropriate, an exception - * should be thrown. - */ - protected void checkParameterValues(Action action) - { - // Intentionally empty - } - - /** - * @param renditionDefinition RenditionDefinition - * @return QName - */ - protected QName getRenditionContentProperty(RenditionDefinition renditionDefinition) - { - return getParamWithDefault(PARAM_TARGET_CONTENT_PROPERTY, defaultRenditionContentProp, renditionDefinition); - } - - protected abstract void render(RenderingContext context); - - /** - * @param actionedUponNodeRef NodeRef - */ - protected void checkSourceNodeExists(NodeRef actionedUponNodeRef) - { - if (nodeService.exists(actionedUponNodeRef) == false) - { - String msg = "Cannot execute action as node does not exist: " + actionedUponNodeRef; - logger.warn(msg); - throw new RenditionServiceException(msg); - } - } - - /** - * @param action Action - */ - protected RenditionDefinition checkActionIsRenditionDefinition(Action action) - { - if (action instanceof RenditionDefinition) - { - return (RenditionDefinition)action; - } - else - { - return new RenditionDefinitionImpl(action); - } - } - - /** - * If no rendition node type is specified, then the default is used - * - * @param renditionDefinition RenditionDefinition - * @return QName - */ - private QName getRenditionNodeType(RenditionDefinition renditionDefinition) - { - return getParamWithDefault(PARAM_RENDITION_NODETYPE, defaultRenditionNodeType, renditionDefinition); - } - - @Override - final protected void addParameterDefinitions(List paramList) - { - paramList.addAll(getParameterDefinitions()); - } - - /** - * This method gets the parameter definition display label from the properties file. - * It looks first for a property whose key has a fixed rendition service-specific - * prefix and if that gets null, it then delegates to the standard bean name-based - * approach. - * - * @param paramName the name of the parameter - * @return the display label of the parameter - */ - @Override - protected String getParamDisplayLabel(String paramName) - { - // First we try to get the message using a common prefix for all rendering engines. - final String commonPropertiesPrefix = "baseRenderingAction"; - String message = I18NUtil.getMessage(commonPropertiesPrefix + "." + paramName + "." + DISPLAY_LABEL); - - // And if that doesn't work we delegate to the standard bean name-based approach. - if (message == null) - { - message = super.getParamDisplayLabel(paramName); - } - return message; - } - - /** - * Supplies the list of parameters required by this rendering engine. - * - */ - protected Collection getParameterDefinitions() - { - List paramList = new ArrayList(); - - paramList.add(new ParameterDefinitionImpl(RenditionDefinitionImpl.RENDITION_DEFINITION_NAME, DataTypeDefinition.QNAME, true, - getParamDisplayLabel(RenditionDefinitionImpl.RENDITION_DEFINITION_NAME))); - - paramList.add(new ParameterDefinitionImpl(PARAM_RUN_AS, DataTypeDefinition.TEXT, false, - getParamDisplayLabel(PARAM_RUN_AS))); - - paramList.add(new ParameterDefinitionImpl(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, DataTypeDefinition.BOOLEAN, false, - getParamDisplayLabel(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE))); - - paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_NODETYPE, DataTypeDefinition.QNAME, false, - getParamDisplayLabel(PARAM_RENDITION_NODETYPE))); - - paramList.add(new ParameterDefinitionImpl(PARAM_PLACEHOLDER_RESOURCE_PATH, DataTypeDefinition.TEXT, false, - getParamDisplayLabel(PARAM_PLACEHOLDER_RESOURCE_PATH))); - - paramList.add(new ParameterDefinitionImpl(PARAM_SOURCE_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, - getParamDisplayLabel(PARAM_SOURCE_CONTENT_PROPERTY))); - - paramList.add(new ParameterDefinitionImpl(PARAM_TARGET_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, - getParamDisplayLabel(PARAM_TARGET_CONTENT_PROPERTY))); - - paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_PATH_TEMPLATE, DataTypeDefinition.TEXT, false, - getParamDisplayLabel(PARAM_DESTINATION_PATH_TEMPLATE))); - - paramList.add(new ParameterDefinitionImpl(PARAM_ORPHAN_EXISTING_RENDITION, DataTypeDefinition.BOOLEAN, false, - getParamDisplayLabel(PARAM_ORPHAN_EXISTING_RENDITION))); - - paramList.add(new ParameterDefinitionImpl(PARAM_RESULT, DataTypeDefinition.CHILD_ASSOC_REF, false, - getParamDisplayLabel(PARAM_RESULT))); - - paramList.add(new ParameterDefinitionImpl(PARAM_IS_COMPONENT_RENDITION, DataTypeDefinition.BOOLEAN, false, - getParamDisplayLabel(PARAM_IS_COMPONENT_RENDITION))); - return paramList; - } - - private ChildAssociationRef createRenditionNodeAssoc(NodeRef sourceNode, RenditionDefinition renditionDefinition) - { - QName renditionName = renditionDefinition.getRenditionName(); - - // The ThumbnailService puts a cm:name property on its thumbnail nodes. - Map nodeProps = new HashMap(); - nodeProps.put(ContentModel.PROP_NAME, renditionName.getLocalName()); - nodeProps.put(ContentModel.PROP_CONTENT_PROPERTY_NAME, getRenditionContentProp(renditionDefinition)); - QName assocName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, GUID.generate()); - NodeRef parentNode = renditionDefinition.getRenditionParent(); - QName assocType = renditionDefinition.getRenditionAssociationType(); - QName nodeType = getRenditionNodeType(renditionDefinition); - - // Ensure that the creation of rendition children does not cause updates - // to the modified, modifier properties on the source node - behaviourFilter.disableBehaviour(parentNode, ContentModel.ASPECT_AUDITABLE); - ChildAssociationRef childAssoc = null; - try - { - childAssoc = nodeService.createNode(parentNode, assocType, assocName, nodeType, nodeProps); - if (logger.isDebugEnabled()) - { - logger.debug("Created node " + childAssoc + " as child of " + parentNode + " with assoc-type " + assocType); - } - } - finally - { - behaviourFilter.enableBehaviour(parentNode, ContentModel.ASPECT_AUDITABLE); - } - return childAssoc; - } - - private Serializable getRenditionContentProp(RenditionDefinition renditionDefinition) - { - return getParamWithDefault(PARAM_TARGET_CONTENT_PROPERTY, getDefaultRenditionContentProp(), renditionDefinition); - } - - /** - * Gets the value for the named parameter. Checks the type of the parameter - * is correct and throws a {@link RenditionServiceException} if it isn't. - * Returns null if the parameter value is null - * - * @param paramName the name of the parameter being checked. - * @param clazz the expected {@link Class} of the parameter value. - * @param definition the {@link RenditionDefinition} containing the - * parameters. - * @return the parameter value or null. - */ - @SuppressWarnings("unchecked") - public static T getCheckedParam(String paramName, Class clazz, RenditionDefinition definition) - { - Serializable value = definition.getParameterValue(paramName); - if (value == null) - return null; - else - { - if(clazz == null) - throw new RenditionServiceException("The class must not be null!", new NullPointerException()); - Class valueClass = value.getClass(); - if ( !clazz.isAssignableFrom(valueClass)) - { - throw new RenditionServiceException("The parameter: " + paramName + " must be of type: " - + clazz.getName() + "but was of type: " + valueClass.getName()); - } - else - return (T) value; - } - } - - /** - * Gets the value for the named parameter. Checks the type of the parameter - * is the same as the type of defaultValue and throws a - * {@link RenditionServiceException} if it isn't. Returns - * defaultValue if the parameter value is null - * - * @param paramName String - * @param defaultValue T - * @param definition RenditionDefinition - * @param T - * @return T - */ - @SuppressWarnings("unchecked") - public static T getParamWithDefault(String paramName, T defaultValue, RenditionDefinition definition) - { - if(defaultValue == null) - throw new RenditionServiceException("The defaultValue cannot be null!", new NullPointerException()); - Class clazz = (Class) defaultValue.getClass(); - T result = getCheckedParam(paramName, clazz, definition); - if (result == null) - result = defaultValue; - return result; - } - - 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 getCheckedParam(String paramName, Class clazz) - { - return AbstractRenderingEngine.getCheckedParam(paramName, clazz, definition); - } - - public 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 - // doesn't already have it. - if (!nodeService.hasAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED)) - { - if (logger.isDebugEnabled()) - { - logger.debug("Applying " + RenditionModel.ASPECT_RENDITIONED + " to " + actionedUponNodeRef); - } - // Ensure we do not update the 'modifier' due to rendition addition - behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); - try - { - nodeService.addAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED, null); - } - finally - { - behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); - } - } - } - - protected void switchToFinalRenditionNode(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef) - { - ChildAssociationRef tempRendAssoc = (ChildAssociationRef)renditionDef.getParameterValue(PARAM_RESULT); - if (logger.isDebugEnabled()) - { - logger.debug("Switching temporary rendition: " + tempRendAssoc); - } - ChildAssociationRef result = createOrUpdateRendition(actionedUponNodeRef, tempRendAssoc, renditionDef); - renditionDef.setParameterValue(PARAM_RESULT, result); - } - - protected void notifyCallbackOfException(RenditionDefinition renditionDefinition, Throwable t) - { - // Rendition has failed. If there is a callback, it needs to be notified - if (renditionDefinition != null) - { - RenderCallback callback = renditionDefinition.getCallback(); - if (callback != null) - { - callback.handleFailedRendition(t); - } - } - } - - protected void throwWrappedException(Throwable t) - { - // and rethrow Exception - if (t instanceof AlfrescoRuntimeException) - { - throw (AlfrescoRuntimeException) t; - } else - { - throw new RenditionServiceException(t.getMessage(), t); - } - } - - protected void notifyCallbackOfResult(RenditionDefinition renditionDefinition, ChildAssociationRef result) - { - // Rendition was successful. Notify the callback object. - if (renditionDefinition != null) - { - RenderCallback callback = renditionDefinition.getCallback(); - if (callback != null) - { - callback.handleSuccessfulRendition(result); - } - } - } - - /** - * This method sets the temporary rendition parent node and the rendition assocType on the - * rendition definition. - * - * @param sourceNode NodeRef - * @param definition the rendition definition. - */ - private void setTemporaryRenditionProperties(NodeRef sourceNode, RenditionDefinition definition) - { - // Set the parent and assoc type for the temporary rendition to be - // created. - NodeRef parent = temporaryParentNodeLocator.getNode(sourceNode, definition.getParameterValues()); - definition.setRenditionParent(parent); - definition.setRenditionAssociationType(temporaryRenditionLinkType); - - if (logger.isDebugEnabled()) - { - StringBuilder msg = new StringBuilder(); - msg.append("Temporary rendition will have parent=").append(parent) - .append(" and assoc-type=").append(temporaryRenditionLinkType); - logger.debug(msg.toString()); - } - } - - /** - * - * @param sourceNode The node that has been rendered - * @param tempRendition The relationship between the node and its rendition - * @param renditionDefinition The definition of the rendition that has just been performed. - * In the case of a composite rendition, this parameter refers - * to that CompositeRendition and not to any of its component renditions. - * @return ChildAssociationRef - */ - private ChildAssociationRef createOrUpdateRendition(NodeRef sourceNode, ChildAssociationRef tempRendition, - RenditionDefinition renditionDefinition) - { - NodeRef tempRenditionNode = tempRendition.getChildRef(); - RenditionLocation renditionLocation = resolveRenditionLocation(sourceNode, renditionDefinition, tempRenditionNode); - - QName renditionQName = renditionDefinition.getRenditionName(); - - RenditionNodeManager renditionNodeManager = new RenditionNodeManager(sourceNode, tempRenditionNode, - renditionLocation, renditionDefinition, nodeService, renditionService, behaviourFilter); - ChildAssociationRef renditionNode = renditionNodeManager.findOrCreateRenditionNode(); - - // Set the name property on the rendition if it has not already been - // set. - String renditionName = getRenditionName(tempRenditionNode, renditionLocation, renditionDefinition); - nodeService.setProperty(renditionNode.getChildRef(), ContentModel.PROP_NAME, renditionName); // to manager - - // Add temporary aspect for temporary rendition node - // When this node id deleted, will not appear in user's trashcan - nodeService.addAspect(tempRendition.getChildRef(), ContentModel.ASPECT_TEMPORARY, null); - // Delete the temporary rendition. - nodeService.removeChildAssociation(tempRendition); - if (logger.isDebugEnabled()) - { - logger.debug("Removed temporary child-association " + tempRendition); - } - - // Handle the rendition aspects - manageRenditionAspects(sourceNode, renditionNode); - - // Verify that everything has gone to plan, and nothing got lost on the way! - ChildAssociationRef renditionAssoc = renditionService.getRenditionByName(sourceNode, renditionQName); - if (renditionAssoc == null) - { - String msg = "A rendition of name: " + renditionQName + " should have been created for source node: " - + sourceNode; - throw new RenditionServiceException(msg); - } - // Return the link between the source and the new, final rendition - return renditionAssoc; - } - - /** - * This method manages the rn:rendition aspects on the rendition node. It applies the - * correct rendition aspect based on the rendition node's location and removes any out-of-date rendition - * aspect. - */ - private void manageRenditionAspects(NodeRef sourceNode, ChildAssociationRef renditionParentAssoc) - { - NodeRef renditionNode = renditionParentAssoc.getChildRef(); - NodeRef primaryParent = renditionParentAssoc.getParentRef(); - - // If the rendition is located directly underneath its own source node - if (primaryParent.equals(sourceNode)) - { - // It should be a 'hidden' rendition. - // Ensure we do not update the 'modifier' due to rendition addition - behaviourFilter.disableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); - try - { - nodeService.addAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION, null); - nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION); - } - finally - { - behaviourFilter.enableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); - } - // We remove the other aspect to cover the potential case where a - // rendition - // has been updated in a different location. - } else - { - // Renditions stored underneath any node other than their source are - // 'visible'. - behaviourFilter.disableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); - try - { - nodeService.addAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION, null); - nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION); - } - finally - { - behaviourFilter.enableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); - } - } - } - - /** - * This method calculates the name for a rendition node. The following approaches are attempted in - * the order given below. - *
    - *
  1. If a name is defined in the {@link RenditionLocation} then that is used.
  2. - *
  3. If the temporary rendition has a cm:name value, then that is used.
  4. - *
  5. Otherwise use the rendition definition's rendition name.
  6. - *
- * - * @param tempRenditionNode the temporary rendition node. - * @param location a RenditionLocation struct. - * @param renditionDefinition the rendition definition. - * @return the name for the rendition. - */ - private String getRenditionName(NodeRef tempRenditionNode, RenditionLocation location, - RenditionDefinition renditionDefinition) - { - // If a location name is set then use it. - String locName = location.getChildName(); - if (locName != null && locName.length() > 0) - { - return locName; - } - // Else if the temporary rendition specifies a name property use that. - Serializable tempName = nodeService.getProperty(tempRenditionNode, ContentModel.PROP_NAME); - if (tempName != null) - { - return (String) tempName; - } - // Otherwise use the rendition definition local name. - return renditionDefinition.getRenditionName().getLocalName(); - } - - /** - * Given a rendition definition, a source node and a temporary rendition node, this method uses a - * {@link RenditionLocationResolver} to calculate the {@link RenditionLocation} of the rendition. - */ - protected RenditionLocation resolveRenditionLocation(NodeRef sourceNode, RenditionDefinition definition, - NodeRef tempRendition) - { - return renditionLocationResolver.getRenditionLocation(sourceNode, definition, tempRendition); - } - - /** - * Gets the ExecutionSummary for the given renderingContext - * from the {@link ActionTrackingService}. - *

- * Note that multiple summaries of the same action instance are not currently supported. - * @param renderingContext the rendering context - * @return the found summary or null - */ - protected ExecutionSummary getExecutionSummary(RenderingContext renderingContext) - { - List executionSummaries = actionTrackingService.getExecutingActions(renderingContext.getDefinition()); - if (executionSummaries == null || executionSummaries.size() == 0) - { - return null; - } - if (executionSummaries.size() > 1) - { - throw new ActionServiceException("getExecutionSummary not supported for " + - "multiple instances of the same action"); - } - return executionSummaries.iterator().next(); - } -} +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2025 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +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.rendition2.RenditionService2Impl; +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; + +/** + * This class adds some new behaviour to the standard ActionExecuterAbstractBase in order to support the RenditionService. + * + * @author Neil McErlean + * @author Nick Smith + * @since 3.3 + * + * @deprecated The RenditionService is being replace by the simpler async RenditionService2. + */ +@Deprecated +public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase +{ + + /** Logger */ + private static Log logger = LogFactory.getLog(AbstractRenderingEngine.class); + + 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(); + + // A word on the default* fields below: + // + // RenditionExecuters can be executed with or without two optional + // parameters: "rendition node type" + // and a "rendition content property" parameter. + // These parameters can be specified on a per-action basis. + // If no value is specified, then the default is used. + // That default can be injected via Spring. + // If no default is injected via spring, then there is a "default default" + // for the two params + + /** + * This is the default default node type for renditions - used if no value is injected from spring. + */ + private static final QName DEFAULT_DEFAULT_RENDITION_NODE_TYPE = ContentModel.TYPE_CONTENT; + + /** + * This is the default default property used to specify where rendition content is stored - used if no value is injected from spring. + */ + private static final QName DEFAULT_DEFAULT_RENDITION_CONTENT_PROP = RenditionService2Impl.DEFAULT_RENDITION_CONTENT_PROP; + private static final String DEFAULT_MIMETYPE = RenditionService2Impl.DEFAULT_MIMETYPE; + private static final String DEFAULT_ENCODING = RenditionService2Impl.DEFAULT_ENCODING; + + /** + * This is the default node type that is used when creating rendition objects. + */ + private QName defaultRenditionNodeType = DEFAULT_DEFAULT_RENDITION_NODE_TYPE; + + /** + * This is the default property that is used to store rendition objects' content. + */ + private QName defaultRenditionContentProp = DEFAULT_DEFAULT_RENDITION_CONTENT_PROP; + + /** + * This is the default content property. + */ + private static final QName DEFAULT_CONTENT_PROPERTY = ContentModel.TYPE_CONTENT; + + /* Injected Services */ + protected ContentService contentService; + protected MimetypeMap mimetypeMap; + protected ActionTrackingService actionTrackingService; + protected NamespaceService namespaceService; + + /* Parameter names common to all Rendering Actions */ + /** + * This optional {@link String} parameter specifies the location of a classpath resource which can be used as a placeholder while a rendition is being generated. For example, this might be a simple icon to indicate a rendition is not yet available. This is intended to be used in conjunction with asynchronous generation of renditions. + */ + public static final String PARAM_PLACEHOLDER_RESOURCE_PATH = "placeHolderResourcePath"; + + /** + * This optional {@link QName} parameter specifies which property the Rendering Engine uses to read content from the source node in order to create a rendition. By default this property will be cm:content. + */ + public static final String PARAM_SOURCE_CONTENT_PROPERTY = "sourceContentProperty"; + + /** + * This optional {@link QName} parameter specifies which property the Rendering Engine uses to write content to the rendition node. By default the property used is cm:content. + */ + public static final String PARAM_TARGET_CONTENT_PROPERTY = "targetContentProperty"; + + /** + * This optional {@link Boolean} flag property specifies whether a rendition should be updated automatically if the source node changes. If set to true then the rendition will be re-rendered any time any property changes occur on the source node. This parameter defaults to false. + */ + public static final String PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE = "update-renditions-on-any-property-change"; + + /** + * This optional {@link String} parameter specifies what user permissions are used when creating a rendition. By default the system user is used. + */ + public static final String PARAM_RUN_AS = "runAs"; + + // mime-type is not a common parameter on all Rendering Actions, but it is + // common to many and is used in some common handling code in this class. + /** + * This optional {@link String} parameter specifies the mime type of the rendition content. This defaults to the mime type of the source node content. + */ + public static final String PARAM_MIME_TYPE = "mime-type"; + + /** + * This optional {@link String} paramter specifies the encoding used to create the rendition content. The derfault encoding is UTF-8. + */ + public static final String PARAM_ENCODING = "encoding"; + + /** + * Default {@link NodeLocator} simply returns the source node. + */ + private final static NodeLocator defaultNodeLocator = new SelfNodeLocator(); + + /* Injected beans */ + private RenditionLocationResolver renditionLocationResolver; + protected NodeService nodeService; + private RenditionService renditionService; + private BehaviourFilter behaviourFilter; + + private final NodeLocator temporaryParentNodeLocator; + private final QName temporaryRenditionLinkType; + + /** + * Injects the nodeService bean. + * + * @param nodeService + * the nodeService. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Injects the renditionService bean. + * + * @param renditionService + * RenditionService + */ + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + /** + * @param behaviourFilter + * policy behaviour filter + */ + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setRenditionLocationResolver(RenditionLocationResolver renditionLocationResolver) + { + this.renditionLocationResolver = renditionLocationResolver; + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public AbstractRenderingEngine(NodeLocator temporaryParentNodeLocator, QName temporaryRenditionLinkType) + { + this.publicAction = false; + this.temporaryParentNodeLocator = temporaryParentNodeLocator != null ? temporaryParentNodeLocator + : defaultNodeLocator; + this.temporaryRenditionLinkType = temporaryRenditionLinkType != null ? temporaryRenditionLinkType + : RenditionModel.ASSOC_RENDITION; + } + + public AbstractRenderingEngine() + { + this(null, null); + } + + /** + * Sets the default rendition-node type. + * + * @param type + * String + */ + public void setDefaultRenditionNodeType(String type) + { + QName qname; + try + { + qname = QName.createQName(type); + } + catch (NamespaceException nx) + { + if (logger.isErrorEnabled()) + { + logger.error("Error when setting default rendition node type: ", nx); + } + throw nx; + } + + if (logger.isInfoEnabled()) + { + logger.info("Using default rendition node type: " + qname); + } + this.defaultRenditionNodeType = qname; + } + + /** + * This method returns the type of the default rendition node type. + * + * @return the QName representing the type of the default rendition node type. + */ + protected QName getDefaultRenditionNodeType() + { + return defaultRenditionNodeType; + } + + protected String getTargetMimeType(RenderingContext context) + { + return context.getParamWithDefault(PARAM_MIME_TYPE, DEFAULT_MIMETYPE); + } + + protected String getTargetEncoding(RenderingContext context) + { + return context.getParamWithDefault(PARAM_ENCODING, DEFAULT_ENCODING); + } + + /** + * Sets the default rendition content property. + * + * @param prop + * String + */ + public void setDefaultRenditionContentProp(String prop) + { + QName qname; + try + { + qname = QName.createQName(prop); + } + catch (NamespaceException nx) + { + if (logger.isErrorEnabled()) + { + logger.error("Error when setting default rendition content property: ", nx); + } + throw nx; + } + + if (logger.isInfoEnabled()) + { + logger.info("Using default rendition content property: " + qname); + } + this.defaultRenditionContentProp = qname; + } + + /** + * This method returns the QName of the property that defines the location of the rendition content. An example would be cm:content. + * + * @return the QName the property defining the location of the rendition content. + */ + protected QName getDefaultRenditionContentProp() + { + return defaultRenditionContentProp; + } + + /** + * Set the content service + * + * @param contentService + * the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setMimetypeMap(MimetypeMap mimetypeMap) + { + this.mimetypeMap = mimetypeMap; + } + + public void setActionTrackingService(ActionTrackingService actionTrackingService) + { + this.actionTrackingService = actionTrackingService; + } + + @Override + protected ActionDefinition createActionDefinition(String definitionName) + { + return new RenderingEngineDefinitionImpl(definitionName); + } + + @Override + protected void executeImpl(final Action action, final NodeRef sourceNode) + { + executeImpl((RenditionDefinition) action, sourceNode); + } + + protected void executeImpl(final RenditionDefinition renditionDef, final NodeRef sourceNode) + { + // Don't render the nodes without content. + // MNT-10178 + if (!nodeService.exists(sourceNode)) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Rendition has not been created, because the node no longer exists. (sourceNode=" + sourceNode + ")"); + } + notifyCallbackOfException(renditionDef, new UnimportantTransformException("Rendition was cancelled, because the node no longer exists.")); + return; + } + else if (nodeService.getProperty(sourceNode, ContentModel.PROP_CONTENT) == null) + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Rendition has not been created, because the node has no content to render. (sourceNode=" + sourceNode + ")"); + } + notifyCallbackOfException(renditionDef, new UnimportantTransformException("Rendition was cancelled, because the node has no content to render.")); + return; + } + + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Rendering node ").append(sourceNode).append(" with rendition definition ").append( + renditionDef.getRenditionName()); + msg.append("\n").append(" parameters:").append("\n"); + if (renditionDef.getParameterValues().isEmpty() == false) + { + for (String paramKey : renditionDef.getParameterValues().keySet()) + { + msg.append(" ").append(paramKey).append("=").append(renditionDef.getParameterValue(paramKey)).append("\n"); + } + } + else + { + msg.append(" [None]"); + } + logger.debug(msg.toString()); + } + + Serializable runAsParam = renditionDef.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS); + String runAsName = runAsParam == null ? DEFAULT_RUN_AS_NAME : (String) runAsParam; + + // Renditions should all be created by system by default. + // When renditions are created by a user and are to be created under a + // node + // other than the source node, it is possible that the user will not + // have + // permissions to create content under that node. + // For that reason, we execute all rendition actions as system + // by default. + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { + @Override + public Void doWork() throws Exception + { + ChildAssociationRef result = null; + try + { + // Check whether this rendition is a component of a larger CompositeRendition + boolean isComponentRendition = isComponentRendition(renditionDef); + if (isComponentRendition == false) + { + // Request that the rendition is initially created + // as a child of the source node + setTemporaryRenditionProperties(sourceNode, renditionDef); + } + + // Have the concrete implementation do the actual rendition + executeRenditionImpl(renditionDef, sourceNode); + + // + if (isComponentRendition == false) + { + // Add renditioned aspect to the source node + tagSourceNodeAsRenditioned(renditionDef, sourceNode); + + // Currently the rendition is on a temporary node, which may + // have the wrong name on it, and for path based renditions is + // in the wrong place + // So, have the correct node created, and switch everything to use it + switchToFinalRenditionNode(renditionDef, sourceNode); + } + + // Grab a link to the rendition node - it's been saved as a parameter for us + // (Wait until now to fetch in case it was moved) + result = (ChildAssociationRef) renditionDef.getParameterValue(PARAM_RESULT); + } + catch (Throwable t) + { + notifyCallbackOfException(renditionDef, t); + throwWrappedException(t); + } + if (result != null) + { + notifyCallbackOfResult(renditionDef, result); + } + return null; + } + }, runAsName); + } + + /** + * Is this a standalone rendition, or is it a sub-component of a composite rendition? This is false for standalone renditions, AND ALSO false for the main part of a composite rendition. This only returns true if we're currently processing a component of a composite rendition. + * + * @param action + * Action + * @return boolean + */ + private boolean isComponentRendition(Action action) + { + Serializable s = action.getParameterValue(PARAM_IS_COMPONENT_RENDITION); + boolean result = s == null ? false : (Boolean) s; + return result; + } + + protected void executeRenditionImpl(Action action, NodeRef sourceNode) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Executing rendering engine; name:") + .append(this.name).append(", class:") + .append(this.getClass().getName()); + logger.debug(msg.toString()); + } + + checkParameterValues(action); + RenditionDefinition renditionDefinition = checkActionIsRenditionDefinition(action); + checkSourceNodeExists(sourceNode); + + QName targetContentProp = getRenditionContentProperty(renditionDefinition); + + RenderingContext context = new RenderingContext(sourceNode, + renditionDefinition, + targetContentProp); + render(context); + // This is a workaround for the fact that actions don't have return + // values. + action.getParameterValues().put(PARAM_RESULT, context.getChildAssociationRef()); + } + + /** + * This method can be overridden by subclasses to provide checking of parameter values. If a parameter value is illegal or inappropriate, an exception should be thrown. + */ + protected void checkParameterValues(Action action) + { + // Intentionally empty + } + + /** + * @param renditionDefinition + * RenditionDefinition + * @return QName + */ + protected QName getRenditionContentProperty(RenditionDefinition renditionDefinition) + { + return getParamWithDefault(PARAM_TARGET_CONTENT_PROPERTY, defaultRenditionContentProp, renditionDefinition); + } + + protected abstract void render(RenderingContext context); + + /** + * @param actionedUponNodeRef + * NodeRef + */ + protected void checkSourceNodeExists(NodeRef actionedUponNodeRef) + { + if (nodeService.exists(actionedUponNodeRef) == false) + { + String msg = "Cannot execute action as node does not exist: " + actionedUponNodeRef; + logger.warn(msg); + throw new RenditionServiceException(msg); + } + } + + /** + * @param action + * Action + */ + protected RenditionDefinition checkActionIsRenditionDefinition(Action action) + { + if (action instanceof RenditionDefinition) + { + return (RenditionDefinition) action; + } + else + { + return new RenditionDefinitionImpl(action); + } + } + + /** + * If no rendition node type is specified, then the default is used + * + * @param renditionDefinition + * RenditionDefinition + * @return QName + */ + private QName getRenditionNodeType(RenditionDefinition renditionDefinition) + { + return getParamWithDefault(PARAM_RENDITION_NODETYPE, defaultRenditionNodeType, renditionDefinition); + } + + @Override + final protected void addParameterDefinitions(List paramList) + { + paramList.addAll(getParameterDefinitions()); + } + + /** + * This method gets the parameter definition display label from the properties file. It looks first for a property whose key has a fixed rendition service-specific prefix and if that gets null, it then delegates to the standard bean name-based approach. + * + * @param paramName + * the name of the parameter + * @return the display label of the parameter + */ + @Override + protected String getParamDisplayLabel(String paramName) + { + // First we try to get the message using a common prefix for all rendering engines. + final String commonPropertiesPrefix = "baseRenderingAction"; + String message = I18NUtil.getMessage(commonPropertiesPrefix + "." + paramName + "." + DISPLAY_LABEL); + + // And if that doesn't work we delegate to the standard bean name-based approach. + if (message == null) + { + message = super.getParamDisplayLabel(paramName); + } + return message; + } + + /** + * Supplies the list of parameters required by this rendering engine. + * + */ + protected Collection getParameterDefinitions() + { + List paramList = new ArrayList(); + + paramList.add(new ParameterDefinitionImpl(RenditionDefinitionImpl.RENDITION_DEFINITION_NAME, DataTypeDefinition.QNAME, true, + getParamDisplayLabel(RenditionDefinitionImpl.RENDITION_DEFINITION_NAME))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RUN_AS, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_RUN_AS))); + + paramList.add(new ParameterDefinitionImpl(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_NODETYPE, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_RENDITION_NODETYPE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_PLACEHOLDER_RESOURCE_PATH, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_PLACEHOLDER_RESOURCE_PATH))); + + paramList.add(new ParameterDefinitionImpl(PARAM_SOURCE_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_SOURCE_CONTENT_PROPERTY))); + + paramList.add(new ParameterDefinitionImpl(PARAM_TARGET_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_TARGET_CONTENT_PROPERTY))); + + paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_PATH_TEMPLATE, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_DESTINATION_PATH_TEMPLATE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_ORPHAN_EXISTING_RENDITION, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_ORPHAN_EXISTING_RENDITION))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RESULT, DataTypeDefinition.CHILD_ASSOC_REF, false, + getParamDisplayLabel(PARAM_RESULT))); + + paramList.add(new ParameterDefinitionImpl(PARAM_IS_COMPONENT_RENDITION, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_IS_COMPONENT_RENDITION))); + return paramList; + } + + private ChildAssociationRef createRenditionNodeAssoc(NodeRef sourceNode, RenditionDefinition renditionDefinition) + { + QName renditionName = renditionDefinition.getRenditionName(); + + // The ThumbnailService puts a cm:name property on its thumbnail nodes. + Map nodeProps = new HashMap(); + nodeProps.put(ContentModel.PROP_NAME, renditionName.getLocalName()); + nodeProps.put(ContentModel.PROP_CONTENT_PROPERTY_NAME, getRenditionContentProp(renditionDefinition)); + QName assocName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, GUID.generate()); + NodeRef parentNode = renditionDefinition.getRenditionParent(); + QName assocType = renditionDefinition.getRenditionAssociationType(); + QName nodeType = getRenditionNodeType(renditionDefinition); + + // Ensure that the creation of rendition children does not cause updates + // to the modified, modifier properties on the source node + behaviourFilter.disableBehaviour(parentNode, ContentModel.ASPECT_AUDITABLE); + ChildAssociationRef childAssoc = null; + try + { + childAssoc = nodeService.createNode(parentNode, assocType, assocName, nodeType, nodeProps); + if (logger.isDebugEnabled()) + { + logger.debug("Created node " + childAssoc + " as child of " + parentNode + " with assoc-type " + assocType); + } + } + finally + { + behaviourFilter.enableBehaviour(parentNode, ContentModel.ASPECT_AUDITABLE); + } + return childAssoc; + } + + private Serializable getRenditionContentProp(RenditionDefinition renditionDefinition) + { + return getParamWithDefault(PARAM_TARGET_CONTENT_PROPERTY, getDefaultRenditionContentProp(), renditionDefinition); + } + + /** + * Gets the value for the named parameter. Checks the type of the parameter is correct and throws a {@link RenditionServiceException} if it isn't. Returns null if the parameter value is null + * + * @param paramName + * the name of the parameter being checked. + * @param clazz + * the expected {@link Class} of the parameter value. + * @param definition + * the {@link RenditionDefinition} containing the parameters. + * @return the parameter value or null. + */ + @SuppressWarnings("unchecked") + public static T getCheckedParam(String paramName, Class clazz, RenditionDefinition definition) + { + Serializable value = definition.getParameterValue(paramName); + if (value == null) + return null; + else + { + if (clazz == null) + throw new RenditionServiceException("The class must not be null!", new NullPointerException()); + Class valueClass = value.getClass(); + if (!clazz.isAssignableFrom(valueClass)) + { + throw new RenditionServiceException("The parameter: " + paramName + " must be of type: " + + clazz.getName() + "but was of type: " + valueClass.getName()); + } + else + return (T) value; + } + } + + /** + * Gets the value for the named parameter. Checks the type of the parameter is the same as the type of defaultValue and throws a {@link RenditionServiceException} if it isn't. Returns defaultValue if the parameter value is null + * + * @param paramName + * String + * @param defaultValue + * T + * @param definition + * RenditionDefinition + * @param + * T + * @return T + */ + @SuppressWarnings("unchecked") + public static T getParamWithDefault(String paramName, T defaultValue, RenditionDefinition definition) + { + if (defaultValue == null) + throw new RenditionServiceException("The defaultValue cannot be null!", new NullPointerException()); + Class clazz = (Class) defaultValue.getClass(); + T result = getCheckedParam(paramName, clazz, definition); + if (result == null) + result = defaultValue; + return result; + } + + 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 getCheckedParam(String paramName, Class clazz) + { + return AbstractRenderingEngine.getCheckedParam(paramName, clazz, definition); + } + + public 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 + // doesn't already have it. + if (!nodeService.hasAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED)) + { + if (logger.isDebugEnabled()) + { + logger.debug("Applying " + RenditionModel.ASPECT_RENDITIONED + " to " + actionedUponNodeRef); + } + // Ensure we do not update the 'modifier' due to rendition addition + behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.addAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED, null); + } + finally + { + behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); + } + } + } + + protected void switchToFinalRenditionNode(final RenditionDefinition renditionDef, final NodeRef actionedUponNodeRef) + { + ChildAssociationRef tempRendAssoc = (ChildAssociationRef) renditionDef.getParameterValue(PARAM_RESULT); + if (logger.isDebugEnabled()) + { + logger.debug("Switching temporary rendition: " + tempRendAssoc); + } + ChildAssociationRef result = createOrUpdateRendition(actionedUponNodeRef, tempRendAssoc, renditionDef); + renditionDef.setParameterValue(PARAM_RESULT, result); + } + + protected void notifyCallbackOfException(RenditionDefinition renditionDefinition, Throwable t) + { + // Rendition has failed. If there is a callback, it needs to be notified + if (renditionDefinition != null) + { + RenderCallback callback = renditionDefinition.getCallback(); + if (callback != null) + { + callback.handleFailedRendition(t); + } + } + } + + protected void throwWrappedException(Throwable t) + { + // and rethrow Exception + if (t instanceof AlfrescoRuntimeException) + { + throw (AlfrescoRuntimeException) t; + } + else + { + throw new RenditionServiceException(t.getMessage(), t); + } + } + + protected void notifyCallbackOfResult(RenditionDefinition renditionDefinition, ChildAssociationRef result) + { + // Rendition was successful. Notify the callback object. + if (renditionDefinition != null) + { + RenderCallback callback = renditionDefinition.getCallback(); + if (callback != null) + { + callback.handleSuccessfulRendition(result); + } + } + } + + /** + * This method sets the temporary rendition parent node and the rendition assocType on the rendition definition. + * + * @param sourceNode + * NodeRef + * @param definition + * the rendition definition. + */ + private void setTemporaryRenditionProperties(NodeRef sourceNode, RenditionDefinition definition) + { + // Set the parent and assoc type for the temporary rendition to be + // created. + NodeRef parent = temporaryParentNodeLocator.getNode(sourceNode, definition.getParameterValues()); + definition.setRenditionParent(parent); + definition.setRenditionAssociationType(temporaryRenditionLinkType); + + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Temporary rendition will have parent=").append(parent) + .append(" and assoc-type=").append(temporaryRenditionLinkType); + logger.debug(msg.toString()); + } + } + + /** + * + * @param sourceNode + * The node that has been rendered + * @param tempRendition + * The relationship between the node and its rendition + * @param renditionDefinition + * The definition of the rendition that has just been performed. In the case of a composite rendition, this parameter refers to that CompositeRendition and not to any of its component renditions. + * @return ChildAssociationRef + */ + private ChildAssociationRef createOrUpdateRendition(NodeRef sourceNode, ChildAssociationRef tempRendition, + RenditionDefinition renditionDefinition) + { + NodeRef tempRenditionNode = tempRendition.getChildRef(); + RenditionLocation renditionLocation = resolveRenditionLocation(sourceNode, renditionDefinition, tempRenditionNode); + + QName renditionQName = renditionDefinition.getRenditionName(); + + RenditionNodeManager renditionNodeManager = new RenditionNodeManager(sourceNode, tempRenditionNode, + renditionLocation, renditionDefinition, nodeService, renditionService, behaviourFilter); + ChildAssociationRef renditionNode = renditionNodeManager.findOrCreateRenditionNode(); + + // Set the name property on the rendition if it has not already been + // set. + String renditionName = getRenditionName(tempRenditionNode, renditionLocation, renditionDefinition); + nodeService.setProperty(renditionNode.getChildRef(), ContentModel.PROP_NAME, renditionName); // to manager + + // Add temporary aspect for temporary rendition node + // When this node id deleted, will not appear in user's trashcan + nodeService.addAspect(tempRendition.getChildRef(), ContentModel.ASPECT_TEMPORARY, null); + // Delete the temporary rendition. + nodeService.removeChildAssociation(tempRendition); + if (logger.isDebugEnabled()) + { + logger.debug("Removed temporary child-association " + tempRendition); + } + + // Handle the rendition aspects + manageRenditionAspects(sourceNode, renditionNode); + + // Verify that everything has gone to plan, and nothing got lost on the way! + ChildAssociationRef renditionAssoc = renditionService.getRenditionByName(sourceNode, renditionQName); + if (renditionAssoc == null) + { + String msg = "A rendition of name: " + renditionQName + " should have been created for source node: " + + sourceNode; + if (logger.isDebugEnabled()) + { + logger.debug(msg); + } + // Check if the node has the applied renditioned aspect, and if it does, + // remove the existing rendition node and assign the newly created rendition node. + if (nodeService.hasAspect(sourceNode, RenditionModel.ASPECT_RENDITIONED)) + { + List renditions = nodeService.getChildAssocs(sourceNode, RenditionModel.ASSOC_RENDITION, renditionQName); + if (!renditions.isEmpty()) + { + ChildAssociationRef existingRendition = renditions.get(0); + nodeService.removeChild(sourceNode, existingRendition.getChildRef()); + renditionAssoc = renditionNode; + if (logger.isDebugEnabled()) + { + logger.debug("Removing the existing rendition node that doesn't have contentData and " + + "assigning the newly created rendition node: " + renditionAssoc); + + } + } + } + } + // Return the link between the source and the new, final rendition + return renditionAssoc; + } + + /** + * This method manages the rn:rendition aspects on the rendition node. It applies the correct rendition aspect based on the rendition node's location and removes any out-of-date rendition aspect. + */ + private void manageRenditionAspects(NodeRef sourceNode, ChildAssociationRef renditionParentAssoc) + { + NodeRef renditionNode = renditionParentAssoc.getChildRef(); + NodeRef primaryParent = renditionParentAssoc.getParentRef(); + + // If the rendition is located directly underneath its own source node + if (primaryParent.equals(sourceNode)) + { + // It should be a 'hidden' rendition. + // Ensure we do not update the 'modifier' due to rendition addition + behaviourFilter.disableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.addAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION, null); + nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION); + } + finally + { + behaviourFilter.enableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); + } + // We remove the other aspect to cover the potential case where a + // rendition + // has been updated in a different location. + } + else + { + // Renditions stored underneath any node other than their source are + // 'visible'. + behaviourFilter.disableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); + try + { + nodeService.addAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION, null); + nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION); + } + finally + { + behaviourFilter.enableBehaviour(renditionNode, ContentModel.ASPECT_AUDITABLE); + } + } + } + + /** + * This method calculates the name for a rendition node. The following approaches are attempted in the order given below. + *

    + *
  1. If a name is defined in the {@link RenditionLocation} then that is used.
  2. + *
  3. If the temporary rendition has a cm:name value, then that is used.
  4. + *
  5. Otherwise use the rendition definition's rendition name.
  6. + *
+ * + * @param tempRenditionNode + * the temporary rendition node. + * @param location + * a RenditionLocation struct. + * @param renditionDefinition + * the rendition definition. + * @return the name for the rendition. + */ + private String getRenditionName(NodeRef tempRenditionNode, RenditionLocation location, + RenditionDefinition renditionDefinition) + { + // If a location name is set then use it. + String locName = location.getChildName(); + if (locName != null && locName.length() > 0) + { + return locName; + } + // Else if the temporary rendition specifies a name property use that. + Serializable tempName = nodeService.getProperty(tempRenditionNode, ContentModel.PROP_NAME); + if (tempName != null) + { + return (String) tempName; + } + // Otherwise use the rendition definition local name. + return renditionDefinition.getRenditionName().getLocalName(); + } + + /** + * Given a rendition definition, a source node and a temporary rendition node, this method uses a {@link RenditionLocationResolver} to calculate the {@link RenditionLocation} of the rendition. + */ + protected RenditionLocation resolveRenditionLocation(NodeRef sourceNode, RenditionDefinition definition, + NodeRef tempRendition) + { + return renditionLocationResolver.getRenditionLocation(sourceNode, definition, tempRendition); + } + + /** + * Gets the ExecutionSummary for the given renderingContext from the {@link ActionTrackingService}. + *

+ * Note that multiple summaries of the same action instance are not currently supported. + * + * @param renderingContext + * the rendering context + * @return the found summary or null + */ + protected ExecutionSummary getExecutionSummary(RenderingContext renderingContext) + { + List executionSummaries = actionTrackingService.getExecutingActions(renderingContext.getDefinition()); + if (executionSummaries == null || executionSummaries.size() == 0) + { + return null; + } + if (executionSummaries.size() > 1) + { + throw new ActionServiceException("getExecutionSummary not supported for " + + "multiple instances of the same action"); + } + return executionSummaries.iterator().next(); + } +} diff --git a/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java b/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java index 8807d6724e..b2fd768e7a 100644 --- a/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java +++ b/repository/src/test/java/org/alfresco/repo/rendition2/RenditionService2IntegrationTest.java @@ -717,4 +717,63 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati renditionService2.setEnabled(true); } } + + @Test + public void testRecreationOfRendition2() + { + renditionService2.setEnabled(true); + try + { + NodeRef sourceNodeRef = createSource(ADMIN, "quick.docx"); + assertNotNull("Node not generated", sourceNodeRef); + + // Get content hash code for the source node. + int sourceNodeContentHashCode = getSourceContentHashCode(sourceNodeRef); + + // Trigger the pdf rendition. + render(ADMIN, sourceNodeRef, PDF); + NodeRef pdfRenditionNodeRef = waitForRendition(ADMIN, sourceNodeRef, PDF, true); + assertNotNull("pdf rendition was not generated", pdfRenditionNodeRef); + assertNotNull("pdf rendition was not generated", + nodeService.getProperty(pdfRenditionNodeRef, PROP_CONTENT)); + + // Check the pdf rendition content hash code is valid + int pdfRenditionContentHashCode = getRenditionContentHashCode(pdfRenditionNodeRef); + assertEquals("pdf rendition content hash code is different from source node content hash code", + sourceNodeContentHashCode, pdfRenditionContentHashCode); + + // Calling 'clearRenditionContentData' method directly so that rendition content will be cleaned. + AuthenticationUtil.runAs( + (AuthenticationUtil.RunAsWork) () -> transactionService.getRetryingTransactionHelper() + .doInTransaction(() -> { + renditionService2.clearRenditionContentData(sourceNodeRef, PDF); + return null; + }), + ADMIN); + + assertNull("Rendition has content", nodeService.getProperty(pdfRenditionNodeRef, PROP_CONTENT)); + + pdfRenditionContentHashCode = getRenditionContentHashCode(pdfRenditionNodeRef); + assertFalse("Rendition has content hash code", + isValidRenditionContentHashCode(pdfRenditionContentHashCode)); + + renditionService2.setEnabled(false); + + final QName pdfRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "pdf"); + transactionService.getRetryingTransactionHelper() + .doInTransaction(() -> AuthenticationUtil.runAs( + () -> renditionService.render(sourceNodeRef, pdfRendDefQName), ADMIN)); + + renditionService2.setEnabled(true); + + NodeRef pdfRecreatedRenditionNodeRef = waitForRendition(ADMIN, sourceNodeRef, PDF, true); + assertNotEquals(" Rendition before deletion and after previewing are identical", + pdfRenditionNodeRef.getId(), pdfRecreatedRenditionNodeRef.getId()); + } + finally + { + renditionService2.setEnabled(true); + } + + } }