/*
* #%L
* Alfresco Records Management Module
* %%
* 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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.module.org_alfresco_module_rm.hold;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.audit.RecordsManagementAuditService;
import org.alfresco.module.org_alfresco_module_rm.audit.event.AuditEvent;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService;
import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.annotation.Behaviour;
import org.alfresco.repo.policy.annotation.BehaviourBean;
import org.alfresco.repo.policy.annotation.BehaviourKind;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.ParameterCheck;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Hold service implementation
*
* @author Tuna Aksoy
* @since 2.2
*/
@BehaviourBean
public class HoldServiceImpl extends ServiceBaseImpl
implements HoldService,
NodeServicePolicies.BeforeDeleteNodePolicy,
RecordsManagementModel
{
/** Logger */
private static Log logger = LogFactory.getLog(HoldServiceImpl.class);
/** Audit event keys */
private static final String AUDIT_ADD_TO_HOLD = "addToHold";
private static final String AUDIT_REMOVE_FROM_HOLD = "removeFromHold";
/** File Plan Service */
private FilePlanService filePlanService;
/** Record Service */
private RecordService recordService;
/** Record folder service */
private RecordFolderService recordFolderService;
/** Permission service */
private PermissionService permissionService;
/** records management audit service */
private RecordsManagementAuditService recordsManagementAuditService;
/**
* Set the file plan service
*
* @param filePlanService the file plan service
*/
public void setFilePlanService(FilePlanService filePlanService)
{
this.filePlanService = filePlanService;
}
/**
* Set the node service
*
* @param nodeService the node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* Set the record service
*
* @param recordService the record service
*/
public void setRecordService(RecordService recordService)
{
this.recordService = recordService;
}
/**
* Set the record folder service
*
* @param recordFolderService the record folder service
*/
public void setRecordFolderService(RecordFolderService recordFolderService)
{
this.recordFolderService = recordFolderService;
}
/**
* Set the permission service
*
* @param permissionService the permission services
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* @param recordsManagementAuditService records management audit service
*/
public void setRecordsManagementAuditService(RecordsManagementAuditService recordsManagementAuditService)
{
this.recordsManagementAuditService = recordsManagementAuditService;
}
/**
* Initialise hold service
*/
public void init()
{
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
recordsManagementAuditService.registerAuditEvent(new AuditEvent(AUDIT_ADD_TO_HOLD, "capability.AddToHold.title"));
recordsManagementAuditService.registerAuditEvent(new AuditEvent(AUDIT_REMOVE_FROM_HOLD, "capability.RemoveFromHold.title"));
return null;
}
});
}
/**
* Behaviour unfreezes node's that will no longer he held after delete.
*
* @see org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy#beforeDeleteNode(org.alfresco.service.cmr.repository.NodeRef)
*/
@Behaviour(kind=BehaviourKind.CLASS, type="rma:hold", notificationFrequency=NotificationFrequency.EVERY_EVENT)
@Override
public void beforeDeleteNode(final NodeRef hold)
{
if (nodeService.exists(hold) && isHold(hold))
{
RunAsWork<Void> work = new RunAsWork<Void>()
{
@Override
public Void doWork()
{
List<NodeRef> frozenNodes = getHeld(hold);
for (NodeRef frozenNode : frozenNodes)
{
removeFreezeAspect(frozenNode, 1);
}
return null;
}
};
// run as system user
authenticationUtil.runAsSystem(work);
}
}
/**
* Helper method removes the freeze aspect from the record and record folder if it is no longer
* in a hold.
*
* @param nodeRef
*/
private void removeFreezeAspect(NodeRef nodeRef, int index)
{
List<NodeRef> otherHolds = heldBy(nodeRef, true);
if (otherHolds.size() == index)
{
if (nodeService.hasAspect(nodeRef, ASPECT_FROZEN))
{
// remove the freeze aspect from the node
nodeService.removeAspect(nodeRef, ASPECT_FROZEN);
}
if (isRecordFolder(nodeRef))
{
List<NodeRef> records = recordService.getRecords(nodeRef);
for (NodeRef record : records)
{
if (nodeService.hasAspect(record, ASPECT_FROZEN))
{
List<NodeRef> recordsOtherHolds = heldBy(record, true);
if (recordsOtherHolds.size() == index)
{
// remove the freeze aspect from the node
nodeService.removeAspect(record, ASPECT_FROZEN);
}
}
}
}
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#getHolds(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public List<NodeRef> getHolds(NodeRef filePlan)
{
ParameterCheck.mandatory("filePlan", filePlan);
List<NodeRef> holds = new ArrayList<NodeRef>();
// get the root hold container
NodeRef holdContainer = filePlanService.getHoldContainer(filePlan);
if (holdContainer != null)
{
// get the children of the root hold container
List<ChildAssociationRef> holdsAssocs = nodeService.getChildAssocs(holdContainer, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
for (ChildAssociationRef holdAssoc : holdsAssocs)
{
NodeRef hold = holdAssoc.getChildRef();
if (isHold(hold))
{
// add to list of holds
holds.add(hold);
}
}
}
return holds;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#heldBy(org.alfresco.service.cmr.repository.NodeRef, boolean)
*/
@SuppressWarnings("unchecked")
@Override
public List<NodeRef> heldBy(NodeRef nodeRef, boolean includedInHold)
{
ParameterCheck.mandatory("nodeRef", nodeRef);
List<NodeRef> result = null;
// get all the immediate parent holds
Set<NodeRef> holdsNotIncludingNodeRef = getParentHolds(nodeRef);
// check whether the record is held by vitue of it's record folder
if (isRecord(nodeRef))
{
List<NodeRef> recordFolders = recordFolderService.getRecordFolders(nodeRef);
for (NodeRef recordFolder : recordFolders)
{
holdsNotIncludingNodeRef.addAll(getParentHolds(recordFolder));
}
}
if (!includedInHold)
{
// invert list to get list of holds that do not contain this node
NodeRef filePlan = filePlanService.getFilePlan(nodeRef);
List<NodeRef> allHolds = getHolds(filePlan);
result = ListUtils.subtract(allHolds, new ArrayList<NodeRef>(holdsNotIncludingNodeRef));
}
else
{
result = new ArrayList<NodeRef>(holdsNotIncludingNodeRef);
}
return result;
}
/**
* Helper method to get holds that are direct parents of the given node.
*
* @param nodeRef node reference
* @return Set<{@link NodeRef}> set of parent holds
*/
private Set<NodeRef> getParentHolds(NodeRef nodeRef)
{
List<ChildAssociationRef> holdsAssocs = nodeService.getParentAssocs(nodeRef, ASSOC_FROZEN_RECORDS, ASSOC_FROZEN_RECORDS);
Set<NodeRef> holds = new HashSet<NodeRef>(holdsAssocs.size());
for (ChildAssociationRef holdAssoc : holdsAssocs)
{
holds.add(holdAssoc.getParentRef());
}
return holds;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#getHold(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
@Override
public NodeRef getHold(NodeRef filePlan, String name)
{
ParameterCheck.mandatory("filePlan", filePlan);
ParameterCheck.mandatory("name", name);
// get the root hold container
NodeRef holdContainer = filePlanService.getHoldContainer(filePlan);
// get the hold by name
NodeRef hold = nodeService.getChildByName(holdContainer, ContentModel.ASSOC_CONTAINS, name);
if (hold != null && !isHold(hold))
{
throw new AlfrescoRuntimeException("Can not get hold, because the named node reference isn't a hold.");
}
return hold;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#getHeld(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public List<NodeRef> getHeld(NodeRef hold)
{
ParameterCheck.mandatory("hold", hold);
List<NodeRef> children = new ArrayList<NodeRef>();
if (!isHold(hold))
{
throw new AlfrescoRuntimeException("Can't get the node's held, because passed node reference isn't a hold. (hold=" + hold.toString() + ")");
}
List<ChildAssociationRef> childAssocs = nodeService.getChildAssocs(hold, ASSOC_FROZEN_RECORDS, RegexQNamePattern.MATCH_ALL);
if (childAssocs != null && !childAssocs.isEmpty())
{
for (ChildAssociationRef childAssociationRef : childAssocs)
{
children.add(childAssociationRef.getChildRef());
}
}
return children;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#createHold(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public NodeRef createHold(NodeRef filePlan, String name, String reason, String description)
{
ParameterCheck.mandatory("filePlan", filePlan);
ParameterCheck.mandatory("name", name);
ParameterCheck.mandatory("reason", reason);
// get the root hold container
NodeRef holdContainer = filePlanService.getHoldContainer(filePlan);
// create map of properties
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(3);
properties.put(ContentModel.PROP_NAME, name);
properties.put(PROP_HOLD_REASON, reason);
if (description != null && !description.isEmpty())
{
properties.put(ContentModel.PROP_DESCRIPTION, description);
}
// create assoc name
QName assocName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name);
// create hold
ChildAssociationRef childAssocRef = nodeService.createNode(holdContainer, ContentModel.ASSOC_CONTAINS, assocName, TYPE_HOLD, properties);
return childAssocRef.getChildRef();
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#getHoldReason(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public String getHoldReason(NodeRef hold)
{
ParameterCheck.mandatory("hold", hold);
String reason = null;
if (nodeService.exists(hold) && isHold(hold))
{
// get the reason
reason = (String)nodeService.getProperty(hold, PROP_HOLD_REASON);
}
return reason;
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#setHoldReason(org.alfresco.service.cmr.repository.NodeRef, java.lang.String)
*/
@Override
public void setHoldReason(NodeRef hold, String reason)
{
ParameterCheck.mandatory("hold", hold);
ParameterCheck.mandatory("reason", reason);
if (nodeService.exists(hold) && isHold(hold))
{
nodeService.setProperty(hold, PROP_HOLD_REASON, reason);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#deleteHold(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void deleteHold(final NodeRef hold)
{
ParameterCheck.mandatory("hold", hold);
if (!isHold(hold))
{
throw new AlfrescoRuntimeException("Can't delete hold, becuase passed node is not a hold. (hold=" + hold.toString() + ")");
}
List<NodeRef> held = AuthenticationUtil.runAsSystem(new RunAsWork<List<NodeRef>>()
{
@Override
public List<NodeRef> doWork()
{
return getHeld(hold);
}
});
List<String> heldNames = new ArrayList<String>();
for (NodeRef nodeRef : held)
{
try
{
if (permissionService.hasPermission(nodeRef, RMPermissionModel.FILING) == AccessStatus.DENIED)
{
heldNames.add((String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME));
}
}
catch (AccessDeniedException ade)
{
throw new AlfrescoRuntimeException("Can't delete hold, because you don't have filling permissions on all the items held within the hold.", ade);
}
}
if (heldNames.size() > 0)
{
StringBuilder sb = new StringBuilder();
for (String name : heldNames)
{
sb.append("\n ");
sb.append("'");
sb.append(name);
sb.append("'");
}
throw new AlfrescoRuntimeException("Can't delete hold, because filing permissions for the following items are needed: " + sb.toString());
}
// delete the hold node
nodeService.deleteNode(hold);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#addToHold(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void addToHold(NodeRef hold, NodeRef nodeRef)
{
ParameterCheck.mandatory("hold", hold);
ParameterCheck.mandatory("nodeRef", nodeRef);
List<NodeRef> holds = new ArrayList<NodeRef>(1);
holds.add(hold);
addToHolds(Collections.unmodifiableList(holds), nodeRef);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#addToHold(org.alfresco.service.cmr.repository.NodeRef, java.util.List)
*/
@Override
public void addToHold(NodeRef hold, List<NodeRef> nodeRefs)
{
ParameterCheck.mandatory("hold", hold);
ParameterCheck.mandatory("nodeRefs", nodeRefs);
for (NodeRef nodeRef : nodeRefs)
{
addToHold(hold, nodeRef);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#addToHolds(java.util.List, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void addToHolds(final List<NodeRef> holds, final NodeRef nodeRef)
{
ParameterCheck.mandatoryCollection("holds", holds);
ParameterCheck.mandatory("nodeRef", nodeRef);
if (!isRecord(nodeRef) && !isRecordFolder(nodeRef))
{
String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
throw new AlfrescoRuntimeException("'" + nodeName + "' is neither a record nor a record folder. Only records or record folders can be added to a hold.");
}
if (permissionService.hasPermission(nodeRef, RMPermissionModel.FILING) == AccessStatus.DENIED)
{
String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
throw new AlfrescoRuntimeException("Filing permission on '" + nodeName + "' is needed.");
}
for (final NodeRef hold : holds)
{
if (!isHold(hold))
{
String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME);
throw new AlfrescoRuntimeException("'" + holdName + "' is not a hold so record folders/records cannot be added.");
}
if (permissionService.hasPermission(hold, RMPermissionModel.FILING) == AccessStatus.DENIED)
{
String nodeName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME);
String holdName = (String) nodeService.getProperty(hold, ContentModel.PROP_NAME);
throw new AlfrescoRuntimeException("'" + nodeName + "' can't be added to the hold container as filing permission for '" + holdName + "' is needed.");
}
// check that the node isn't already in the hold
if (!getHeld(hold).contains(nodeRef))
{
// run as system to ensure we have all the appropriate permissions to perform the manipulations we require
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// gather freeze properties
Map<QName, Serializable> props = new HashMap<QName, Serializable>(2);
props.put(PROP_FROZEN_AT, new Date());
props.put(PROP_FROZEN_BY, AuthenticationUtil.getFullyAuthenticatedUser());
if (!nodeService.hasAspect(nodeRef, ASPECT_FROZEN))
{
// add freeze aspect
nodeService.addAspect(nodeRef, ASPECT_FROZEN, props);
if (logger.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Frozen aspect applied to '").append(nodeRef).append("'.");
logger.debug(msg.toString());
}
}
// Link the record to the hold
nodeService.addChild(hold, nodeRef, ASSOC_FROZEN_RECORDS, ASSOC_FROZEN_RECORDS);
// audit item being added to the hold
recordsManagementAuditService.auditEvent(nodeRef, AUDIT_ADD_TO_HOLD);
// Mark all the folders contents as frozen
if (isRecordFolder(nodeRef))
{
List<NodeRef> records = recordService.getRecords(nodeRef);
for (NodeRef record : records)
{
// no need to freeze if already frozen!
if (!nodeService.hasAspect(record, ASPECT_FROZEN))
{
nodeService.addAspect(record, ASPECT_FROZEN, props);
if (logger.isDebugEnabled())
{
StringBuilder msg = new StringBuilder();
msg.append("Frozen aspect applied to '").append(record).append("'.");
logger.debug(msg.toString());
}
}
}
}
return null;
}
});
}
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#addToHolds(java.util.List, java.util.List)
*/
@Override
public void addToHolds(List<NodeRef> holds, List<NodeRef> nodeRefs)
{
ParameterCheck.mandatoryCollection("holds", holds);
ParameterCheck.mandatoryCollection("nodeRefs", nodeRefs);
for (NodeRef nodeRef : nodeRefs)
{
addToHolds(holds, nodeRef);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromHold(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void removeFromHold(NodeRef hold, NodeRef nodeRef)
{
ParameterCheck.mandatory("hold", hold);
ParameterCheck.mandatory("nodeRef", nodeRef);
List<NodeRef> holds = new ArrayList<NodeRef>(1);
holds.add(hold);
removeFromHolds(Collections.unmodifiableList(holds), nodeRef);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromHold(org.alfresco.service.cmr.repository.NodeRef, java.util.List)
*/
@Override
public void removeFromHold(NodeRef hold, List<NodeRef> nodeRefs)
{
ParameterCheck.mandatory("hold", hold);
ParameterCheck.mandatory("nodeRefs", nodeRefs);
for (NodeRef nodeRef : nodeRefs)
{
removeFromHold(hold, nodeRef);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromHolds(java.util.List, org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void removeFromHolds(List<NodeRef> holds, final NodeRef nodeRef)
{
ParameterCheck.mandatory("holds", holds);
ParameterCheck.mandatory("nodeRef", nodeRef);
if (holds != null && !holds.isEmpty())
{
for (final NodeRef hold : holds)
{
if (!instanceOf(hold, TYPE_HOLD))
{
throw new AlfrescoRuntimeException("Can't remove from hold, because it isn't a hold. (hold=" + hold + ")");
}
if (getHeld(hold).contains(nodeRef))
{
// run as system so we don't run into further permission issues
// we already know we have to have the correct capability to get here
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// remove from hold
nodeService.removeChild(hold, nodeRef);
// audit that the node has been remove from the hold
// TODO add details of the hold that the node was removed from
recordsManagementAuditService.auditEvent(nodeRef, AUDIT_REMOVE_FROM_HOLD);
return null;
}
});
}
}
// run as system as we can't be sure if have remove aspect rights on node
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
removeFreezeAspect(nodeRef, 0);
return null;
}
});
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromHolds(java.util.List, java.util.List)
*/
@Override
public void removeFromHolds(List<NodeRef> holds, List<NodeRef> nodeRefs)
{
ParameterCheck.mandatoryCollection("holds", holds);
ParameterCheck.mandatoryCollection("nodeRefs", nodeRefs);
for (NodeRef nodeRef : nodeRefs)
{
removeFromHolds(holds, nodeRef);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromAllHolds(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
public void removeFromAllHolds(NodeRef nodeRef)
{
ParameterCheck.mandatory("nodeRef", nodeRef);
// remove the node from all the holds it's held by
List<NodeRef> holds = heldBy(nodeRef, true);
for (NodeRef hold : holds)
{
// remove node from hold
removeFromHold(hold, nodeRef);
}
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.hold.HoldService#removeFromAllHolds(java.util.List)
*/
@Override
public void removeFromAllHolds(List<NodeRef> nodeRefs)
{
ParameterCheck.mandatory("nodeRefs", nodeRefs);
for (NodeRef nodeRef : nodeRefs)
{
removeFromAllHolds(nodeRef);
}
}
}