2013-08-20 17:17:31 +00:00

650 lines
24 KiB
Java

/*
* Copyright (C) 2005-2013 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.rendition;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.executer.ActionExecuter;
import org.alfresco.repo.coci.CheckOutCheckInServicePolicies.BeforeCheckOut;
import org.alfresco.repo.lock.LockServicePolicies.BeforeLock;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
import org.alfresco.service.cmr.rendition.RenderCallback;
import org.alfresco.service.cmr.rendition.RenderingEngineDefinition;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionPreventedException;
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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/*
* @author Nick Smith
* @author Neil McErlean
* @since 3.3
*/
public class RenditionServiceImpl implements
RenditionService,
RenditionDefinitionPersister,
BeforeCheckOut,
BeforeLock
{
private static final Log log = LogFactory.getLog(RenditionServiceImpl.class);
private ActionService actionService;
private ActionTrackingService actionTrackingService;
private ContentService contentService;
private DictionaryService dictionaryService;
private NodeService nodeService;
private PolicyComponent policyComponent;
private RenditionDefinitionPersister renditionDefinitionPersister;
/**
* @since 4.0.1
*/
private RenditionPreventionRegistry renditionPreventionRegistry;
/**
* @since 4.1.6
*/
private List<String> knownCancellableActionTypes;
/**
* Injects the RenditionDefinitionPersister bean.
* @param renditionDefinitionPersister
*/
public void setRenditionDefinitionPersister(RenditionDefinitionPersister renditionDefinitionPersister)
{
this.renditionDefinitionPersister = renditionDefinitionPersister;
}
/**
* @since 4.0.1
*/
public void setRenditionPreventionRegistry(RenditionPreventionRegistry registry)
{
this.renditionPreventionRegistry = registry;
}
/**
* Injects the ServiceRegistry bean.
* @param serviceRegistry
*/
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.contentService = serviceRegistry.getContentService();
this.nodeService = serviceRegistry.getNodeService();
}
/**
* Injects the ActionService bean.
* @param actionService
*/
public void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
/**
* Injects the ActionTrackingService bean.
* @param actionTrackingService
*/
public void setActionTrackingService(ActionTrackingService actionTrackingService)
{
this.actionTrackingService = actionTrackingService;
}
/**
* Injects the DictionaryService bean.
* @param dictionaryService
*/
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
/**
* Injects the PolicyComponent bean.
* @param policyComponent
*/
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* Sets the list of known cancellable actions used by {@link #cancelRenditions(NodeRef)}.
* @param knownCancellableActionTypes
* @since 4.1.6
*/
public void setKnownCancellableActionTypes(List<String> knownCancellableActionTypes)
{
this.knownCancellableActionTypes = knownCancellableActionTypes;
}
public void init()
{
this.policyComponent.bindClassBehaviour(
BeforeCheckOut.QNAME,
ContentModel.TYPE_CONTENT,
new JavaBehaviour(this, "beforeCheckOut"));
this.policyComponent.bindClassBehaviour(
BeforeLock.QNAME,
ContentModel.TYPE_CONTENT,
new JavaBehaviour(this, "beforeLock"));
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinition(java.lang.String)
*/
public RenderingEngineDefinition getRenderingEngineDefinition(String name)
{
ActionDefinition actionDefinition = actionService.getActionDefinition(name);
if (actionDefinition instanceof RenderingEngineDefinition)
{
return (RenderingEngineDefinition) actionDefinition;
}
else
return null;
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinitions()
*/
public List<RenderingEngineDefinition> getRenderingEngineDefinitions()
{
List<RenderingEngineDefinition> results = new ArrayList<RenderingEngineDefinition>();
List<ActionDefinition> actionDefs = actionService.getActionDefinitions();
for (ActionDefinition actionDef : actionDefs)
{
if (actionDef instanceof RenderingEngineDefinition)
{
RenderingEngineDefinition renderingDef = (RenderingEngineDefinition) actionDef;
results.add(renderingDef);
}
}
return results;
}
/*
* (non-Javadoc)
* @see
* org.alfresco.service.cmr.rendition.RenditionService#createRenditionDefinition
* (org.alfresco.service.namespace.QName, java.lang.String)
*/
public RenditionDefinition createRenditionDefinition(QName renditionDefinitionName, String renderingEngineName)
{
if (log.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Creating rendition definition ")
.append(renditionDefinitionName)
.append(" ")
.append(renderingEngineName);
log.debug(msg.toString());
}
return new RenditionDefinitionImpl(GUID.generate(), renditionDefinitionName, renderingEngineName);
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#createCompositeRenditionDefinition(org.alfresco.service.namespace.QName)
*/
public CompositeRenditionDefinition createCompositeRenditionDefinition(QName renditionName)
{
if (log.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Creating composite rendition definition ")
.append(renditionName);
log.debug(msg.toString());
}
return new CompositeRenditionDefinitionImpl(GUID.generate(), renditionName);
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rendition.RenditionDefinition)
*/
public ChildAssociationRef render(NodeRef sourceNode, RenditionDefinition definition)
{
checkSourceNodeForPreventionClass(sourceNode);
ChildAssociationRef result = executeRenditionAction(sourceNode, definition, false);
if (log.isDebugEnabled())
{
log.debug("Produced rendition " + result);
}
return result;
}
public void render(NodeRef sourceNode, RenditionDefinition definition,
RenderCallback callback)
{
checkSourceNodeForPreventionClass(sourceNode);
// The asynchronous render can't return a ChildAssociationRef as it is created
// asynchronously after this method returns.
definition.setCallback(callback);
executeRenditionAction(sourceNode, definition, true);
return;
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
*/
public ChildAssociationRef render(NodeRef sourceNode, final QName renditionDefinitionQName)
{
checkSourceNodeForPreventionClass(sourceNode);
RenditionDefinition rendDefn = AuthenticationUtil.runAs(
new AuthenticationUtil.RunAsWork<RenditionDefinition>()
{
public RenditionDefinition doWork() throws Exception
{
return loadRenditionDefinition(renditionDefinitionQName);
}
}, AuthenticationUtil.getSystemUserName());
if (rendDefn == null)
{
throw new RenditionServiceException("Rendition Definition " + renditionDefinitionQName + " was not found.");
}
return this.render(sourceNode, rendDefn);
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, org.alfresco.service.cmr.rendition.RenderCallback)
*/
public void render(NodeRef sourceNode, final QName renditionDefinitionQName, RenderCallback callback)
{
checkSourceNodeForPreventionClass(sourceNode);
RenditionDefinition rendDefn = AuthenticationUtil.runAs(
new AuthenticationUtil.RunAsWork<RenditionDefinition>()
{
public RenditionDefinition doWork() throws Exception
{
return loadRenditionDefinition(renditionDefinitionQName);
}
}, AuthenticationUtil.getSystemUserName());
if (rendDefn == null)
{
throw new RenditionServiceException("Rendition Definition " + renditionDefinitionQName + " was not found.");
}
this.render(sourceNode, rendDefn, callback);
}
/**
* This method checks whether the specified source node is of a content class which has been registered for rendition prevention.
*
* @param sourceNode the node to check.
* @throws RenditionPreventedException if the source node is configured for rendition prevention.
* @since 4.0.1
* @see RenditionPreventionRegistry
*/
private void checkSourceNodeForPreventionClass(NodeRef sourceNode)
{
// A node's content class is its type and all its aspects.
// We'll not check the source node for null and leave that to the rendering action.
if (sourceNode != null && nodeService.exists(sourceNode))
{
Set<QName> nodeContentClasses = nodeService.getAspects(sourceNode);
nodeContentClasses.add(nodeService.getType(sourceNode));
for (QName contentClass : nodeContentClasses)
{
if (renditionPreventionRegistry.isContentClassRegistered(contentClass))
{
StringBuilder msg = new StringBuilder();
msg.append("Node ").append(sourceNode)
.append(" cannot be renditioned as it is of class ").append(contentClass);
if (log.isDebugEnabled())
{
log.debug(msg.toString());
}
throw new RenditionPreventedException(msg.toString());
}
}
}
}
/**
* This method delegates the execution of the specified RenditionDefinition
* to the {@link ActionService action service}.
*
* @param sourceNode the source node which is to be rendered.
* @param definition the rendition definition to be used.
* @param asynchronous <code>true</code> for asynchronous execution,
* <code>false</code> for synchronous.
* @return the ChildAssociationRef whose child is the rendition node.
*/
private ChildAssociationRef executeRenditionAction(NodeRef sourceNode,
RenditionDefinition definition, boolean asynchronous)
{
if (log.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
if (asynchronous)
{
msg.append("Asynchronously");
}
else
{
msg.append("Synchronously");
}
msg.append(" rendering node ").append(sourceNode)
.append(" with ").append(definition.getRenditionName());
log.debug(msg.toString());
}
final boolean checkConditions = true;
actionService.executeAction(definition, sourceNode, checkConditions, asynchronous);
ChildAssociationRef result = (ChildAssociationRef)definition.getParameterValue(ActionExecuter.PARAM_RESULT);
return result;
}
/*
* (non-Javadoc)
* @see
* org.alfresco.service.cmr.rendition.RenditionService#saveRenditionDefinition
* (org.alfresco.service.cmr.rendition.RenditionDefinition)
*/
public void saveRenditionDefinition(RenditionDefinition renderingAction)
{
this.renditionDefinitionPersister.saveRenditionDefinition(renderingAction);
}
/*
* @see
* org.alfresco.service.cmr.rendition.RenditionService#loadRenderingAction
* (org.alfresco.service.namespace.QName)
*/
public RenditionDefinition loadRenditionDefinition(QName renditionDefinitionName)
{
return this.renditionDefinitionPersister.loadRenditionDefinition(renditionDefinitionName);
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#loadRenditionDefinitions()
*/
public List<RenditionDefinition> loadRenditionDefinitions()
{
return this.renditionDefinitionPersister.loadRenditionDefinitions();
}
/*
* (non-Javadoc)
* @see
* org.alfresco.service.cmr.rendition.RenditionService#loadRenderingActions
* (java.lang.String)
*/
public List<RenditionDefinition> loadRenditionDefinitions(String renditionEngineName)
{
return this.renditionDefinitionPersister.loadRenditionDefinitions(renditionEngineName);
}
/*
* (non-Javadoc)
* @see
* org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org
* .alfresco.service.cmr.repository.NodeRef)
*/
public List<ChildAssociationRef> getRenditions(NodeRef node)
{
List<ChildAssociationRef> result = Collections.emptyList();
// Check that the node has the renditioned aspect applied
if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true)
{
// Get all the renditions that match the given rendition name
result = nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL);
result = removeArchivedRenditionsFrom(result);
}
return result;
}
private List<ChildAssociationRef> removeArchivedRenditionsFrom(List<ChildAssociationRef> renditionAssocs)
{
// This is a workaround for a bug in the NodeService (no JIRA number yet) whereby a call to
// nodeService.getChildAssocs can return all children, including children in the archive store.
List<ChildAssociationRef> result = new ArrayList<ChildAssociationRef>();
for (ChildAssociationRef chAssRef : renditionAssocs)
{
// If the rendition has *not* been deleted, then it should remain in the result list.
if (chAssRef.getChildRef().getStoreRef().equals(StoreRef.STORE_REF_ARCHIVE_SPACESSTORE) == false)
{
result.add(chAssRef);
}
}
return result;
}
/*
* (non-Javadoc)
* @see
* org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org
* .alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
public List<ChildAssociationRef> getRenditions(NodeRef node, String mimeTypePrefix)
{
List<ChildAssociationRef> allRenditions = this.getRenditions(node);
List<ChildAssociationRef> filteredResults = new ArrayList<ChildAssociationRef>();
for (ChildAssociationRef chAssRef : allRenditions)
{
NodeRef renditionNode = chAssRef.getChildRef();
QName contentProperty = ContentModel.PROP_CONTENT;
Serializable contentPropertyName = nodeService.getProperty(renditionNode,
ContentModel.PROP_CONTENT_PROPERTY_NAME);
if (contentPropertyName != null)
{
contentProperty = (QName) contentPropertyName;
}
ContentReader reader = contentService.getReader(renditionNode, contentProperty);
if (reader != null && reader.exists())
{
String readerMimeType = reader.getMimetype();
if (readerMimeType.startsWith(mimeTypePrefix))
{
filteredResults.add(chAssRef);
}
}
}
filteredResults = removeArchivedRenditionsFrom(filteredResults);
return filteredResults;
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#getRenditionByName(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
*/
public ChildAssociationRef getRenditionByName(NodeRef node, QName renditionName)
{
List<ChildAssociationRef> renditions = Collections.emptyList();
// Check that the node has the renditioned aspect applied
if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true)
{
// Get all the renditions that match the given rendition name -
// there should only be 1 (or 0)
renditions = this.nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, renditionName);
renditions = this.removeArchivedRenditionsFrom(renditions);
}
if (renditions.isEmpty())
{
return null;
}
else
{
if (renditions.size() > 1 && log.isDebugEnabled())
{
log.debug("Unexpectedly found " + renditions.size() + " renditions of name " + renditionName + " on node " + node);
}
return renditions.get(0);
}
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#isRendition(org.alfresco.service.cmr.repository.NodeRef)
*/
public boolean isRendition(NodeRef node)
{
final QName aspectToCheckFor = RenditionModel.ASPECT_RENDITION;
Set<QName> existingAspects = nodeService.getAspects(node);
for (QName nextAspect : existingAspects)
{
if (nextAspect.equals(aspectToCheckFor) || dictionaryService.isSubClass(nextAspect, aspectToCheckFor))
{
return true;
}
}
return false;
}
/*
* (non-Javadoc)
* @see org.alfresco.service.cmr.rendition.RenditionService#getSourceNode(org.alfresco.service.cmr.repository.NodeRef)
*/
public ChildAssociationRef getSourceNode(NodeRef renditionNode)
{
// In normal circumstances only a node which is itself a rendition can have
// a source node - as linked by the rn:rendition association.
//
// However there are some circumstances where a node which is not
// technically a rendition can still have a source. One such example is the case
// of thumbnail nodes created in a pre-3.3 Alfresco which have not been patched
// to have the correct rendition aspect applied.
// This will also occur *during* execution of the webscript patch and so the
// decision was made not to throw an exception or log a warning if such a
// situation is encountered.
// A rendition node should have 1 and only 1 source node.
List<ChildAssociationRef> parents = nodeService.getParentAssocs(renditionNode,
RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL);
if (parents.size() > 1)
{
StringBuilder msg = new StringBuilder();
msg.append("NodeRef ")
.append(renditionNode)
.append(" unexpectedly has ")
.append(parents.size())
.append(" rendition parents.");
if (log.isWarnEnabled())
{
log.warn(msg.toString());
}
throw new RenditionServiceException(msg.toString());
}
else
{
return parents.isEmpty() ? null : parents.get(0);
}
}
public void cancelRenditions(NodeRef sourceNode)
{
if (knownCancellableActionTypes == null)
{
return;
}
for (String type : knownCancellableActionTypes)
{
cancelRenditions(sourceNode, type);
}
}
public void cancelRenditions(NodeRef sourceNode, String type)
{
if (log.isDebugEnabled())
{
log.debug("cancelling renditions of type " + type + " on nodeRef: " + sourceNode.toString());
}
List<ExecutionSummary> executionSummaries = actionTrackingService.getExecutingActions(type, sourceNode);
for (ExecutionSummary executionSummary : executionSummaries)
{
actionTrackingService.requestActionCancellation(executionSummary);
}
}
@Override
public void beforeCheckOut(
NodeRef nodeRef,
NodeRef destinationParentNodeRef,
QName destinationAssocTypeQName,
QName destinationAssocQName)
{
cancelRenditions(nodeRef);
}
@Override
public void beforeLock(NodeRef nodeRef, LockType lockType)
{
cancelRenditions(nodeRef);
}
}