/*
* #%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.model.rma.aspect;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies;
import org.alfresco.module.org_alfresco_module_rm.model.behaviour.AbstractDisposableItem;
import org.alfresco.module.org_alfresco_module_rm.record.RecordService;
import org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService;
import org.alfresco.repo.content.ContentServicePolicies;
import org.alfresco.repo.copy.CopyBehaviourCallback;
import org.alfresco.repo.copy.CopyDetails;
import org.alfresco.repo.copy.CopyServicePolicies;
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.integrity.IntegrityException;
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.RunAsWork;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.namespace.QName;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* rma:record behaviour bean
*
* @author Roy Wetherall
* @since 2.2
*/
@BehaviourBean
(
defaultType = "rma:record"
)
public class RecordAspect extends AbstractDisposableItem
implements NodeServicePolicies.OnCreateChildAssociationPolicy,
RecordsManagementPolicies.OnCreateReference,
RecordsManagementPolicies.OnRemoveReference,
NodeServicePolicies.OnMoveNodePolicy,
CopyServicePolicies.OnCopyCompletePolicy,
ContentServicePolicies.OnContentPropertyUpdatePolicy
{
/** Well-known location of the scripts folder. */
// TODO make configurable
private NodeRef scriptsFolderNodeRef = new NodeRef("workspace", "SpacesStore", "rm_behavior_scripts");
/** extended security service */
protected ExtendedSecurityService extendedSecurityService;
/** script service */
protected ScriptService scriptService;
/** record service */
protected RecordService recordService;
/** I18N */
private static final String MSG_CANNOT_UPDATE_RECORD_CONTENT = "rm.service.update-record-content";
/**
* @param extendedSecurityService extended security service
*/
public void setExtendedSecurityService(ExtendedSecurityService extendedSecurityService)
{
this.extendedSecurityService = extendedSecurityService;
}
/**
* @param scriptService script service
*/
public void setScriptService(ScriptService scriptService)
{
this.scriptService = scriptService;
}
/**
* @param recordService record service
*/
public void setRecordService(RecordService recordService)
{
this.recordService = recordService;
}
/**
* Behaviour to ensure renditions have the appropriate extended security.
*
* @see org.alfresco.repo.node.NodeServicePolicies.OnCreateChildAssociationPolicy#onCreateChildAssociation(org.alfresco.service.cmr.repository.ChildAssociationRef, boolean)
*/
@Override
@Behaviour
(
kind = BehaviourKind.ASSOCIATION,
assocType = "rn:rendition",
notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT
)
public void onCreateChildAssociation(final ChildAssociationRef childAssocRef, boolean bNew)
{
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
NodeRef thumbnail = childAssocRef.getChildRef();
if (nodeService.exists(thumbnail))
{
// apply file plan component aspect to thumbnail
nodeService.addAspect(thumbnail, ASPECT_FILE_PLAN_COMPONENT, null);
// manage any extended readers
NodeRef parent = childAssocRef.getParentRef();
Set<String> readers = extendedSecurityService.getReaders(parent);
Set<String> writers = extendedSecurityService.getWriters(parent);
if (readers != null && readers.size() != 0)
{
extendedSecurityService.set(thumbnail, readers, writers);
}
}
return null;
}
});
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.OnCreateReference#onCreateReference(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT
)
public void onCreateReference(final NodeRef fromNodeRef, NodeRef toNodeRef, QName reference)
{
// Deal with versioned records
if (reference.equals(CUSTOM_REF_VERSIONS))
{
// run as system, to apply the versioned aspect to the from node
// as we can't be sure if the user has add aspect rights
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
nodeService.addAspect(fromNodeRef, ASPECT_VERSIONED_RECORD, null);
return null;
}
});
}
// Execute script if for the reference event
executeReferenceScript("onCreate", reference, fromNodeRef, toNodeRef);
}
/**
* @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.OnRemoveReference#onRemoveReference(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT
)
public void onRemoveReference(final NodeRef fromNodeRef, NodeRef toNodeRef, QName reference)
{
// Deal with versioned records
if (reference.equals(CUSTOM_REF_VERSIONS))
{
authenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@Override
public Void doWork()
{
// Apply the versioned aspect to the from node
nodeService.removeAspect(fromNodeRef, ASPECT_VERSIONED_RECORD);
return null;
}
});
}
// Execute script if for the reference event
executeReferenceScript("onRemove", reference, fromNodeRef, toNodeRef);
}
/**
* Record copy callback
*/
@Behaviour
(
kind = BehaviourKind.CLASS,
policy = "alf:getCopyCallback"
)
public CopyBehaviourCallback getCopyCallback(final QName classRef, final CopyDetails copyDetails)
{
return new DefaultCopyBehaviourCallback()
{
@Override
public Map<QName, Serializable> getCopyProperties(QName classRef, CopyDetails copyDetails,
Map<QName, Serializable> properties)
{
Map<QName, Serializable> sourceProperties = super.getCopyProperties(classRef, copyDetails, properties);
// Remove the Date Filed property from record properties on copy.
// It will be generated for the copy
if (sourceProperties.containsKey(PROP_DATE_FILED))
{
sourceProperties.remove(PROP_DATE_FILED);
}
return sourceProperties;
}
};
}
/**
* Record move behaviour
*
* @see org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy#onMoveNode(org.alfresco.service.cmr.repository.ChildAssociationRef, org.alfresco.service.cmr.repository.ChildAssociationRef)
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.FIRST_EVENT
)
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
{
// check the records parent has actually changed
if (!oldChildAssocRef.getParentRef().equals(newChildAssocRef.getParentRef()) &&
isFilePlanComponent(oldChildAssocRef.getParentRef()))
{
final NodeRef record = newChildAssocRef.getChildRef();
authenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork()
{
if (nodeService.exists(record) &&
recordService.isFiled(record))
{
// clean record
cleanDisposableItem(nodeService, record);
// re-file in the new folder
recordService.file(record);
}
return null;
}
}, authenticationUtil.getAdminUserName());
}
}
/**
* Executes a reference script if present
*
* @param policy policy
* @param reference reference
* @param from reference from
* @param to reference to
*/
private void executeReferenceScript(String policy, QName reference, NodeRef from, NodeRef to)
{
String referenceId = reference.getLocalName();
// This is the filename pattern which is assumed.
// e.g. a script file onCreate_superceded.js for the creation of a superseded reference
String expectedScriptName = policy + "_" + referenceId + ".js";
NodeRef scriptNodeRef = nodeService.getChildByName(scriptsFolderNodeRef, ContentModel.ASSOC_CONTAINS, expectedScriptName);
if (scriptNodeRef != null)
{
Map<String, Object> objectModel = new HashMap<String, Object>(1);
objectModel.put("node", from);
objectModel.put("toNode", to);
objectModel.put("policy", policy);
objectModel.put("reference", referenceId);
scriptService.executeScript(scriptNodeRef, null, objectModel);
}
}
/**
* On copy complete behaviour for record aspect.
*
* @param classRef
* @param sourceNodeRef
* @param targetNodeRef
* @param copyToNewNode
* @param copyMap
*/
@Override
@Behaviour
(
kind = BehaviourKind.CLASS
)
public void onCopyComplete(QName classRef,
NodeRef sourceNodeRef,
NodeRef targetNodeRef,
boolean copyToNewNode,
Map<NodeRef, NodeRef> copyMap)
{
// given the node exists and is a record
if (nodeService.exists(targetNodeRef) &&
nodeService.hasAspect(targetNodeRef, ASPECT_RECORD))
{
// then remove any extended security from the newly copied record
extendedSecurityService.remove(targetNodeRef);
}
}
@Override
@Behaviour
(
kind = BehaviourKind.CLASS,
notificationFrequency = NotificationFrequency.FIRST_EVENT
)
public void onContentPropertyUpdate(NodeRef nodeRef, QName propertyQName, ContentData beforeValue, ContentData afterValue)
{
// Allow creation of content but not update
if (beforeValue != null)
{
throw new IntegrityException(I18NUtil.getMessage(MSG_CANNOT_UPDATE_RECORD_CONTENT), null);
}
}
}