mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
91057: Merged V4.2-BUG-FIX (4.2.5) to HEAD-BUG-FIX (5.0/Cloud) 90986: MNT-11732: ephemeral locks have configurable threshold over which locks will be automatically made persistent instead. By setting the new property, administrators may control how ephemeral locks are created, for example: property alfresco.ephemeralLock.expiryThresh=30 This will mean that when a LockService client requests that an ephemeral lock is created with a timeout of greater than 30 seconds, the lock will be created as a persistent lock instead (the ephemeral lifetime request is vetoed). By setting this property to -1, ALL locks will be created as persistent locks (giving the old, pre-ephemeral locking behaviour). By leaving this at the default (48 hours) the newer locking behaviour is completely unaffected, e.g. ephemeral locks may be created with up to 48 hour expiry time and over this limit an exception is thrown (and the lock is not created). By setting the value to something in between these settings it is possible to have quite a nice balance of behaviour. Using the example of 30 seconds, as above, will mean that using Share's "Edit Online" to edit a document in MS Word will result in a persistent lock (as MS Word requests approx 1 hour for its locks). After Solr has performed its next incremental index, then the Word document may be seen in the "Document's I'm Editing" filter in Share, since this is a persistent (in-DB) lock. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94765 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
932 lines
34 KiB
Java
932 lines
34 KiB
Java
/*
|
|
* Copyright (C) 2005-2010 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.lock;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Collection;
|
|
import java.util.Date;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.copy.CopyBehaviourCallback;
|
|
import org.alfresco.repo.copy.CopyDetails;
|
|
import org.alfresco.repo.copy.CopyServicePolicies;
|
|
import org.alfresco.repo.copy.DoNothingCopyBehaviourCallback;
|
|
import org.alfresco.repo.lock.LockServicePolicies.BeforeLock;
|
|
import org.alfresco.repo.lock.mem.Lifetime;
|
|
import org.alfresco.repo.lock.mem.LockState;
|
|
import org.alfresco.repo.lock.mem.LockStore;
|
|
import org.alfresco.repo.lock.mem.LockableAspectInterceptor;
|
|
import org.alfresco.repo.node.NodeServicePolicies;
|
|
import org.alfresco.repo.node.index.NodeIndexer;
|
|
import org.alfresco.repo.policy.BehaviourFilter;
|
|
import org.alfresco.repo.policy.ClassPolicyDelegate;
|
|
import org.alfresco.repo.policy.JavaBehaviour;
|
|
import org.alfresco.repo.policy.PolicyComponent;
|
|
import org.alfresco.repo.policy.PolicyScope;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.tenant.TenantService;
|
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|
import org.alfresco.repo.transaction.TransactionListener;
|
|
import org.alfresco.repo.transaction.TransactionalResourceHelper;
|
|
import org.alfresco.repo.version.VersionServicePolicies;
|
|
import org.alfresco.service.cmr.lock.LockService;
|
|
import org.alfresco.service.cmr.lock.LockStatus;
|
|
import org.alfresco.service.cmr.lock.LockType;
|
|
import org.alfresco.service.cmr.lock.NodeLockedException;
|
|
import org.alfresco.service.cmr.lock.UnableToAquireLockException;
|
|
import org.alfresco.service.cmr.lock.UnableToReleaseLockException;
|
|
import org.alfresco.service.cmr.lock.UnableToReleaseLockException.CAUSE;
|
|
import org.alfresco.service.cmr.repository.AspectMissingException;
|
|
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.repository.StoreRef;
|
|
import org.alfresco.service.cmr.search.ResultSet;
|
|
import org.alfresco.service.cmr.search.SearchService;
|
|
import org.alfresco.service.cmr.security.AuthenticationService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.Pair;
|
|
import org.alfresco.util.PropertyCheck;
|
|
import org.springframework.util.Assert;
|
|
|
|
/**
|
|
* Simple Lock service implementation
|
|
*
|
|
* @author Roy Wetherall
|
|
*/
|
|
public class LockServiceImpl implements LockService,
|
|
NodeServicePolicies.OnCreateChildAssociationPolicy,
|
|
NodeServicePolicies.BeforeUpdateNodePolicy,
|
|
NodeServicePolicies.BeforeDeleteNodePolicy,
|
|
NodeServicePolicies.OnMoveNodePolicy,
|
|
CopyServicePolicies.OnCopyNodePolicy,
|
|
VersionServicePolicies.OnCreateVersionPolicy, TransactionListener
|
|
{
|
|
public static final int MAX_EPHEMERAL_LOCK_SECONDS = 2 * 86400;
|
|
|
|
/** Key to the nodes ref's to ignore when checking for locks */
|
|
private static final String KEY_IGNORE_NODES = "lockService.ignoreNodes";
|
|
private static final Object KEY_MODIFIED_NODES = "lockService.lockedNode";
|
|
|
|
private NodeService nodeService;
|
|
private TenantService tenantService;
|
|
private AuthenticationService authenticationService;
|
|
private SearchService searchService;
|
|
private BehaviourFilter behaviourFilter;
|
|
private LockStore lockStore;
|
|
private PolicyComponent policyComponent;
|
|
private LockableAspectInterceptor lockableAspectInterceptor;
|
|
|
|
/** Class policy delegate's */
|
|
private ClassPolicyDelegate<BeforeLock> beforeLock;
|
|
|
|
private NodeIndexer nodeIndexer;
|
|
|
|
private int ephemeralExpiryThreshold;
|
|
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public void setTenantService(TenantService tenantService)
|
|
{
|
|
this.tenantService = tenantService;
|
|
}
|
|
|
|
public void setLockStore(LockStore lockStore)
|
|
{
|
|
this.lockStore = lockStore;
|
|
}
|
|
|
|
public void setPolicyComponent(PolicyComponent policyComponent)
|
|
{
|
|
this.policyComponent = policyComponent;
|
|
}
|
|
|
|
public void setLockableAspectInterceptor(LockableAspectInterceptor lockableAspectInterceptor)
|
|
{
|
|
this.lockableAspectInterceptor = lockableAspectInterceptor;
|
|
}
|
|
|
|
public void setAuthenticationService(AuthenticationService authenticationService)
|
|
{
|
|
this.authenticationService = authenticationService;
|
|
}
|
|
|
|
public void setSearchService(SearchService searchService)
|
|
{
|
|
this.searchService = searchService;
|
|
}
|
|
|
|
/**
|
|
* Initialise methods called by Spring framework
|
|
*/
|
|
public void init()
|
|
{
|
|
PropertyCheck.mandatory(this, "nodeService", nodeService);
|
|
PropertyCheck.mandatory(this, "tenantService", tenantService);
|
|
PropertyCheck.mandatory(this, "authenticationService", authenticationService);
|
|
PropertyCheck.mandatory(this, "searchService", searchService);
|
|
PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
|
|
PropertyCheck.mandatory(this, "policyComponent", policyComponent);
|
|
|
|
// Register the policies
|
|
beforeLock = policyComponent.registerClassPolicy(LockServicePolicies.BeforeLock.class);
|
|
|
|
// Register the various class behaviours to enable lock checking
|
|
this.policyComponent.bindAssociationBehaviour(
|
|
NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "onCreateChildAssociation"));
|
|
this.policyComponent.bindClassBehaviour(
|
|
NodeServicePolicies.BeforeUpdateNodePolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "beforeUpdateNode"));
|
|
this.policyComponent.bindClassBehaviour(
|
|
NodeServicePolicies.BeforeDeleteNodePolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "beforeDeleteNode"));
|
|
this.policyComponent.bindClassBehaviour(
|
|
NodeServicePolicies.OnMoveNodePolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "onMoveNode"));
|
|
|
|
// Register copy class behaviour
|
|
this.policyComponent.bindClassBehaviour(
|
|
CopyServicePolicies.OnCopyNodePolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "getCopyCallback"));
|
|
|
|
// Register the onCreateVersion behavior for the version aspect
|
|
|
|
// BeforeCreateVersion behavior was removed
|
|
// we should be able to version a node regardless of its lock state, see ALF-16540
|
|
|
|
this.policyComponent.bindClassBehaviour(
|
|
VersionServicePolicies.OnCreateVersionPolicy.QNAME,
|
|
ContentModel.ASPECT_LOCKABLE,
|
|
new JavaBehaviour(this, "onCreateVersion"));
|
|
}
|
|
|
|
/**
|
|
* Returns all the classes of a node, including its type and aspects.
|
|
*
|
|
* @param nodeRef node reference
|
|
* @return List<QName> list of classes
|
|
*/
|
|
private List<QName> getInvokeClasses(NodeRef nodeRef)
|
|
{
|
|
List<QName> result = new ArrayList<QName>(10);
|
|
result.add(nodeService.getType(nodeRef));
|
|
Set<QName> aspects = nodeService.getAspects(nodeRef);
|
|
for (QName aspect : aspects)
|
|
{
|
|
result.add(aspect);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Invoke the before log policy
|
|
*
|
|
* @param nodeRef the node to be locked
|
|
* @param lockType the lock type
|
|
*/
|
|
private void invokeBeforeLock(
|
|
NodeRef nodeRef,
|
|
LockType lockType)
|
|
{
|
|
if (!nodeService.exists(nodeRef))
|
|
{
|
|
return;
|
|
}
|
|
List<QName> classes = getInvokeClasses(nodeRef);
|
|
for (QName invokeClass : classes)
|
|
{
|
|
Collection<BeforeLock> policies = beforeLock.getList(invokeClass);
|
|
for (BeforeLock policy : policies)
|
|
{
|
|
policy.beforeLock(nodeRef, lockType);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private void addToIgnoreSet(NodeRef nodeRef)
|
|
{
|
|
Set<NodeRef> ignoreNodeRefs = (Set<NodeRef>)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
|
|
if (ignoreNodeRefs == null)
|
|
{
|
|
ignoreNodeRefs = new HashSet<NodeRef>();
|
|
AlfrescoTransactionSupport.bindResource(KEY_IGNORE_NODES, ignoreNodeRefs);
|
|
}
|
|
ignoreNodeRefs.add(nodeRef);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private void removeFromIgnoreSet(NodeRef nodeRef)
|
|
{
|
|
Set<NodeRef> ignoreNodeRefs = (Set<NodeRef>)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
|
|
if (ignoreNodeRefs != null)
|
|
{
|
|
ignoreNodeRefs.remove(nodeRef);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private boolean ignore(NodeRef nodeRef)
|
|
{
|
|
Set<NodeRef> ignoreNodeRefs = (Set<NodeRef>)AlfrescoTransactionSupport.getResource(KEY_IGNORE_NODES);
|
|
if (ignoreNodeRefs != null)
|
|
{
|
|
return ignoreNodeRefs.contains(nodeRef);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType)
|
|
*/
|
|
public void lock(NodeRef nodeRef, LockType lockType)
|
|
{
|
|
// Lock with no expiration
|
|
lock(nodeRef, lockType, 0);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType, int)
|
|
*/
|
|
@Override
|
|
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire)
|
|
{
|
|
lock(nodeRef, lockType, timeToExpire, Lifetime.PERSISTENT);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType, int, Lifetime, String)
|
|
*/
|
|
@Override
|
|
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire, Lifetime lifetime)
|
|
{
|
|
lock(nodeRef, lockType, timeToExpire, lifetime, null);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType, int, Lifetime, String)
|
|
*/
|
|
@Override
|
|
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire, Lifetime lifetime, String additionalInfo)
|
|
{
|
|
invokeBeforeLock(nodeRef, lockType);
|
|
if (lifetime.equals(Lifetime.EPHEMERAL) && (timeToExpire > MAX_EPHEMERAL_LOCK_SECONDS))
|
|
{
|
|
throw new IllegalArgumentException("Attempt to create ephemeral lock for " +
|
|
timeToExpire + " seconds - exceeds maximum allowed time.");
|
|
}
|
|
if (lifetime.equals(Lifetime.EPHEMERAL) && (timeToExpire > ephemeralExpiryThreshold))
|
|
{
|
|
lifetime = Lifetime.PERSISTENT;
|
|
}
|
|
|
|
|
|
nodeRef = tenantService.getName(nodeRef);
|
|
|
|
// Get the current user name
|
|
String userName = getUserName();
|
|
|
|
// Set a default value
|
|
if (lockType == null)
|
|
{
|
|
lockType = LockType.WRITE_LOCK;
|
|
}
|
|
|
|
// Get the current lock info and status for the node ref.
|
|
Pair<LockState, LockStatus> statusAndState = getLockStateAndStatus(nodeRef, userName);
|
|
LockState currentLockInfo = statusAndState.getFirst();
|
|
LockStatus currentLockStatus = statusAndState.getSecond();
|
|
|
|
if (LockStatus.LOCKED.equals(currentLockStatus) == true)
|
|
{
|
|
// Error since we are trying to lock a locked node
|
|
throw new UnableToAquireLockException(nodeRef);
|
|
}
|
|
else if (LockStatus.NO_LOCK.equals(currentLockStatus) == true ||
|
|
LockStatus.LOCK_EXPIRED.equals(currentLockStatus) == true ||
|
|
LockStatus.LOCK_OWNER.equals(currentLockStatus) == true)
|
|
{
|
|
final Date expiryDate = makeExpiryDate(timeToExpire);
|
|
|
|
// Store the lock in the appropriate place.
|
|
if (lifetime == Lifetime.PERSISTENT)
|
|
{
|
|
lockableAspectInterceptor.disableForThread();
|
|
try
|
|
{
|
|
// Add lock aspect if not already present
|
|
ensureLockAspect(nodeRef);
|
|
persistLockProps(nodeRef, lockType, lifetime, userName, expiryDate, additionalInfo);
|
|
}
|
|
finally
|
|
{
|
|
lockableAspectInterceptor.enableForThread();
|
|
}
|
|
}
|
|
else if (lifetime == Lifetime.EPHEMERAL)
|
|
{
|
|
// Store the lock only in memory.
|
|
LockState lock = LockState.createLock(nodeRef, lockType, userName,
|
|
expiryDate, lifetime, additionalInfo);
|
|
lockStore.set(nodeRef, lock);
|
|
// Record the NodeRef being locked and its last known lockstate. This allows
|
|
// it to be reverted to this state on rollback.
|
|
TransactionalResourceHelper.getMap(KEY_MODIFIED_NODES).put(nodeRef, currentLockInfo);
|
|
AlfrescoTransactionSupport.bindListener(this);
|
|
|
|
nodeIndexer.indexUpdateNode(nodeRef);
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalStateException(lifetime.getClass().getSimpleName() +
|
|
" is not a valid value: " + lifetime.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void persistLockProps(NodeRef nodeRef, LockType lockType, Lifetime lifetime, String userName, Date expiryDate, String additionalInfo)
|
|
{
|
|
addToIgnoreSet(nodeRef);
|
|
try
|
|
{
|
|
// Set the current user as the lock owner
|
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_OWNER, userName);
|
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_TYPE, lockType.toString());
|
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_LIFETIME, lifetime.toString());
|
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_EXPIRY_DATE, expiryDate);
|
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_ADDITIONAL_INFO, additionalInfo);
|
|
}
|
|
finally
|
|
{
|
|
removeFromIgnoreSet(nodeRef);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate expiry date based on the time to expire provided
|
|
*
|
|
* @param timeToExpire the time to expire (in seconds)
|
|
*/
|
|
private Date makeExpiryDate(int timeToExpire)
|
|
{
|
|
// Set the expiry date
|
|
Date expiryDate = null;
|
|
if (timeToExpire > 0)
|
|
{
|
|
expiryDate = new Date();
|
|
Calendar calendar = Calendar.getInstance();
|
|
calendar.setTime(expiryDate);
|
|
calendar.add(Calendar.SECOND, timeToExpire);
|
|
expiryDate = calendar.getTime();
|
|
}
|
|
return expiryDate;
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(org.alfresco.service.cmr.repository.NodeRef, java.lang.String, org.alfresco.service.cmr.lock.LockType, int, boolean)
|
|
*/
|
|
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire, boolean lockChildren)
|
|
throws UnableToAquireLockException
|
|
{
|
|
lock(nodeRef, lockType, timeToExpire);
|
|
|
|
if (lockChildren == true)
|
|
{
|
|
Collection<ChildAssociationRef> childAssocRefs = this.nodeService.getChildAssocs(nodeRef);
|
|
for (ChildAssociationRef childAssocRef : childAssocRefs)
|
|
{
|
|
lock(childAssocRef.getChildRef(), lockType, timeToExpire, lockChildren);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#lock(java.util.Collection, java.lang.String, org.alfresco.service.cmr.lock.LockType, int)
|
|
*/
|
|
public void lock(Collection<NodeRef> nodeRefs, LockType lockType, int timeToExpire)
|
|
throws UnableToAquireLockException
|
|
{
|
|
// Lock each of the specifed nodes
|
|
for (NodeRef nodeRef : nodeRefs)
|
|
{
|
|
lock(nodeRef, lockType, timeToExpire);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#unlock(NodeRef, String)
|
|
*/
|
|
@Override
|
|
public void unlock(NodeRef nodeRef) throws UnableToReleaseLockException
|
|
{
|
|
unlock(nodeRef, false, false);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#unlock(org.alfresco.service.cmr.repository.NodeRef, boolean)
|
|
*/
|
|
@Override
|
|
public void unlock(NodeRef nodeRef, boolean lockChildren) throws UnableToReleaseLockException
|
|
{
|
|
unlock(nodeRef, lockChildren, false);
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#unlock(NodeRef, String,
|
|
* boolean, boolean)
|
|
*/
|
|
@Override
|
|
public void unlock(NodeRef nodeRef, boolean unlockChildren, boolean allowCheckedOut)
|
|
throws UnableToReleaseLockException
|
|
{
|
|
// Unlock the parent
|
|
nodeRef = tenantService.getName(nodeRef);
|
|
|
|
LockState lockState = getLockState(nodeRef);
|
|
|
|
if (lockState.isLockInfo())
|
|
{
|
|
// MNT-231: forbidden to unlock a checked out node
|
|
if (!allowCheckedOut && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_CHECKED_OUT))
|
|
{
|
|
throw new UnableToReleaseLockException(nodeRef, CAUSE.CHECKED_OUT);
|
|
}
|
|
|
|
// Remove the lock from persistent storage.
|
|
Lifetime lifetime = lockState.getLifetime();
|
|
if (lifetime == Lifetime.PERSISTENT)
|
|
{
|
|
addToIgnoreSet(nodeRef);
|
|
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
|
lockableAspectInterceptor.disableForThread();
|
|
try
|
|
{
|
|
// Clear the lock
|
|
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
nodeService.removeAspect(nodeRef, ContentModel.ASPECT_LOCKABLE);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
|
|
lockableAspectInterceptor.enableForThread();
|
|
removeFromIgnoreSet(nodeRef);
|
|
}
|
|
}
|
|
else if (lifetime == Lifetime.EPHEMERAL)
|
|
{
|
|
// Remove the ephemeral lock.
|
|
lockStore.set(nodeRef, LockState.createUnlocked(nodeRef));
|
|
nodeIndexer.indexUpdateNode(nodeRef);
|
|
}
|
|
else
|
|
{
|
|
throw new IllegalStateException("Unhandled Lifetime value: " + lifetime);
|
|
}
|
|
}
|
|
|
|
if (unlockChildren)
|
|
{
|
|
// Get the children and unlock them
|
|
Collection<ChildAssociationRef> childAssocRefs = this.nodeService.getChildAssocs(nodeRef);
|
|
for (ChildAssociationRef childAssocRef : childAssocRefs)
|
|
{
|
|
unlock(childAssocRef.getChildRef(), unlockChildren);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.repo.lock.LockService#unlock(Collection<NodeRef>,
|
|
* String)
|
|
*/
|
|
public void unlock(Collection<NodeRef> nodeRefs) throws UnableToReleaseLockException
|
|
{
|
|
for (NodeRef nodeRef : nodeRefs)
|
|
{
|
|
unlock(nodeRef);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#getLockStatus(NodeRef)
|
|
*/
|
|
public LockStatus getLockStatus(NodeRef nodeRef)
|
|
{
|
|
nodeRef = tenantService.getName(nodeRef);
|
|
|
|
return getLockStatus(nodeRef, getUserName());
|
|
}
|
|
|
|
/**
|
|
* Gets the lock status for a node and a user name
|
|
*
|
|
* @param nodeRef the node reference
|
|
* @param userName the user name
|
|
* @return the lock status
|
|
*/
|
|
public LockStatus getLockStatus(NodeRef nodeRef, String userName)
|
|
{
|
|
Pair<LockState, LockStatus> stateAndStatus = getLockStateAndStatus(nodeRef, userName);
|
|
LockStatus lockStatus = stateAndStatus.getSecond();
|
|
return lockStatus;
|
|
}
|
|
|
|
private Pair<LockState, LockStatus> getLockStateAndStatus(NodeRef nodeRef, String userName)
|
|
{
|
|
final LockState lockState = getLockState(nodeRef);
|
|
|
|
String lockOwner = lockState.getOwner();
|
|
Date expiryDate = lockState.getExpires();
|
|
LockStatus status = LockUtils.lockStatus(userName, lockOwner, expiryDate);
|
|
return new Pair<LockState, LockStatus>(lockState, status);
|
|
}
|
|
|
|
/**
|
|
* @see LockService#getLockType(NodeRef)
|
|
*/
|
|
public LockType getLockType(NodeRef nodeRef)
|
|
{
|
|
LockType result = null;
|
|
|
|
// Don't disable the lockable aspect interceptor - allow it to fetch the lock type
|
|
// from the correct place (persistent storage or lockStore).
|
|
if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == true)
|
|
{
|
|
String lockTypeString = (String) this.nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_TYPE);
|
|
if (lockTypeString != null)
|
|
{
|
|
result = LockType.valueOf(lockTypeString);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks for the lock aspect. Adds if missing.
|
|
*
|
|
* @param nodeRef
|
|
* the node reference
|
|
*/
|
|
private void ensureLockAspect(NodeRef nodeRef)
|
|
{
|
|
if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false)
|
|
{
|
|
this.nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public void checkForLock(NodeRef nodeRef) throws NodeLockedException
|
|
{
|
|
String userName = getUserName();
|
|
|
|
nodeRef = tenantService.getName(nodeRef);
|
|
|
|
// Ensure we have found a node reference
|
|
if (nodeRef != null && userName != null)
|
|
{
|
|
String effectiveUserName = AuthenticationUtil.getRunAsUser();
|
|
// Check to see if should just ignore this node - note: special MT System due to AuditableAspect
|
|
if (! (ignore(nodeRef) || tenantService.getBaseNameUser(effectiveUserName).equals(AuthenticationUtil.getSystemUserName())))
|
|
{
|
|
try
|
|
{
|
|
// Get the current lock status on the node ref
|
|
LockStatus currentLockStatus = getLockStatus(nodeRef, userName);
|
|
|
|
LockType lockType = getLockType(nodeRef);
|
|
if (LockType.WRITE_LOCK.equals(lockType) == true &&
|
|
LockStatus.LOCKED.equals(currentLockStatus) == true)
|
|
{
|
|
// Lock is of type Write Lock and the node is locked by another owner.
|
|
throw new NodeLockedException(nodeRef);
|
|
}
|
|
else if (LockType.READ_ONLY_LOCK.equals(lockType) == true &&
|
|
(LockStatus.LOCKED.equals(currentLockStatus) == true || LockStatus.LOCK_OWNER.equals(currentLockStatus) == true))
|
|
{
|
|
// Error since there is a read only lock on this object and all
|
|
// modifications are prevented
|
|
throw new NodeLockedException(nodeRef);
|
|
}
|
|
else if (LockType.NODE_LOCK.equals(lockType) == true &&
|
|
(LockStatus.LOCKED.equals(currentLockStatus) == true || LockStatus.LOCK_OWNER.equals(currentLockStatus) == true))
|
|
{
|
|
// Error since there is a read only lock on this object and all
|
|
// modifications are prevented
|
|
throw new NodeLockedException(nodeRef);
|
|
}
|
|
|
|
}
|
|
catch (AspectMissingException exception)
|
|
{
|
|
// Ignore since this indicates that the node does not have the lock aspect applied
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that the parent is not locked.
|
|
*
|
|
* @see #checkForLock(NodeRef)
|
|
*/
|
|
public void onCreateChildAssociation(ChildAssociationRef childAssocRef, boolean isNewNode)
|
|
{
|
|
LockType lockType = getLockType(childAssocRef.getParentRef());
|
|
if(lockType != null)
|
|
{
|
|
|
|
switch (lockType)
|
|
{
|
|
case WRITE_LOCK:
|
|
case READ_ONLY_LOCK:
|
|
checkForLock(childAssocRef.getParentRef());
|
|
break;
|
|
case NODE_LOCK:
|
|
// don't check for lock
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that node is not locked.
|
|
*
|
|
* @see #checkForLock(NodeRef)
|
|
*/
|
|
public void beforeUpdateNode(NodeRef nodeRef)
|
|
{
|
|
checkForLock(nodeRef);
|
|
}
|
|
|
|
/**
|
|
* Ensures that node is not locked.
|
|
*
|
|
* @see #checkForLock(NodeRef)
|
|
*/
|
|
public void beforeDeleteNode(NodeRef nodeRef)
|
|
{
|
|
checkForLock(nodeRef);
|
|
}
|
|
|
|
/**
|
|
* @return Returns {@link DoNothingCopyBehaviourCallback}
|
|
*/
|
|
public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails)
|
|
{
|
|
return DoNothingCopyBehaviourCallback.getInstance();
|
|
}
|
|
|
|
/**
|
|
* OnCreateVersion behaviour for the lock aspect
|
|
* <p>
|
|
* Ensures that the property values of the lock aspect are not 'frozen' in
|
|
* the version store.
|
|
*/
|
|
public void onCreateVersion(
|
|
QName classRef,
|
|
NodeRef versionableNode,
|
|
Map<String, Serializable> versionProperties,
|
|
PolicyScope nodeDetails)
|
|
{
|
|
// Add the lock aspect, but do not version the property values
|
|
// TODO: disable the LockAspectInterceptor for this thread, re-enable in finally.
|
|
// (we need to add this aspect for real).
|
|
nodeDetails.addAspect(ContentModel.ASPECT_LOCKABLE);
|
|
}
|
|
|
|
/**
|
|
* Get the current user reference
|
|
*
|
|
* @return the current user reference
|
|
*/
|
|
private String getUserName()
|
|
{
|
|
return this.authenticationService.getCurrentUserName();
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#getLocks()
|
|
* @deprecated Uses search and does not report on ephemeral locks.
|
|
*/
|
|
@Deprecated
|
|
public List<NodeRef> getLocks(StoreRef storeRef)
|
|
{
|
|
return getLocks(
|
|
storeRef,
|
|
"ASPECT:\"" + ContentModel.ASPECT_LOCKABLE.toString() +
|
|
"\" +@\\{http\\://www.alfresco.org/model/content/1.0\\}" + ContentModel.PROP_LOCK_OWNER.getLocalName() + ":\"" + getUserName() + "\"");
|
|
}
|
|
|
|
/**
|
|
* Get the locks given a store and query string.
|
|
*
|
|
* @param storeRef the store reference
|
|
* @param query the query string
|
|
* @return the locked nodes
|
|
* @deprecated Uses search and does not report on ephemeral locks.
|
|
*/
|
|
@Deprecated
|
|
private List<NodeRef> getLocks(StoreRef storeRef, String query)
|
|
{
|
|
List<NodeRef> result = new ArrayList<NodeRef>();
|
|
ResultSet resultSet = null;
|
|
try
|
|
{
|
|
resultSet = this.searchService.query(
|
|
storeRef,
|
|
SearchService.LANGUAGE_LUCENE,
|
|
query);
|
|
result = resultSet.getNodeRefs();
|
|
}
|
|
finally
|
|
{
|
|
if (resultSet != null)
|
|
{
|
|
resultSet.close();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @see org.alfresco.service.cmr.lock.LockService#getLocks(org.alfresco.service.cmr.lock.LockType)
|
|
* @deprecated Uses search and does not report on ephemeral locks.
|
|
*/
|
|
@Deprecated
|
|
public List<NodeRef> getLocks(StoreRef storeRef, LockType lockType)
|
|
{
|
|
return getLocks(
|
|
storeRef,
|
|
"ASPECT:\"" + ContentModel.ASPECT_LOCKABLE.toString() +
|
|
"\" +@\\{http\\://www.alfresco.org/model/content/1.0\\}" + ContentModel.PROP_LOCK_OWNER.getLocalName() + ":\"" + getUserName() + "\"" +
|
|
" +@\\{http\\://www.alfresco.org/model/content/1.0\\}" + ContentModel.PROP_LOCK_TYPE.getLocalName() + ":\"" + lockType.toString() + "\"");
|
|
}
|
|
|
|
@Override
|
|
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
|
|
{
|
|
NodeRef nodeRef = oldChildAssocRef.getChildRef();
|
|
checkForLock(nodeRef);
|
|
}
|
|
|
|
@Override
|
|
public void suspendLocks()
|
|
{
|
|
getBehaviourFilter().disableBehaviour(ContentModel.ASPECT_LOCKABLE);
|
|
lockableAspectInterceptor.disableForThread();
|
|
}
|
|
|
|
@Override
|
|
public void enableLocks()
|
|
{
|
|
getBehaviourFilter().enableBehaviour(ContentModel.ASPECT_LOCKABLE);
|
|
lockableAspectInterceptor.enableForThread();
|
|
}
|
|
|
|
@Override
|
|
public String getAdditionalInfo(NodeRef nodeRef)
|
|
{
|
|
LockState lockState = getLockState(nodeRef);
|
|
String additionalInfo = lockState.getAdditionalInfo();
|
|
return additionalInfo;
|
|
}
|
|
|
|
@Override
|
|
public LockState getLockState(NodeRef nodeRef)
|
|
{
|
|
// Check in-memory for ephemeral locks first.
|
|
nodeRef = tenantService.getName(nodeRef);
|
|
LockState lockState = lockStore.get(nodeRef);
|
|
|
|
//ALF-20361: It is possible that a rollback has resulted in a "non-lock" lock state being added to
|
|
//the lock store. Because of that, we check both whether the retrieved lockState is null and, if it isn't,
|
|
//whether it represents a real lock
|
|
if (lockState == null || !lockState.isLockInfo())
|
|
{
|
|
// No in-memory state, so get from the DB.
|
|
if (nodeService.exists(nodeRef) && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
|
|
{
|
|
String lockOwner = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_OWNER);
|
|
|
|
Date expiryDate = (Date) nodeService.getProperty(nodeRef, ContentModel.PROP_EXPIRY_DATE);
|
|
String lockTypeStr = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_TYPE);
|
|
LockType lockType = lockTypeStr != null ? LockType.valueOf(lockTypeStr) : null;
|
|
String lifetimeStr = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_LIFETIME);
|
|
Lifetime lifetime = lifetimeStr != null ? Lifetime.valueOf(lifetimeStr) : null;
|
|
String additionalInfo = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_ADDITIONAL_INFO);
|
|
|
|
// Mark lockstate as PERSISTENT as it was in the persistent storage!
|
|
lockState = LockState.createLock(
|
|
nodeRef,
|
|
lockType,
|
|
lockOwner,
|
|
expiryDate,
|
|
lifetime,
|
|
additionalInfo);
|
|
}
|
|
else
|
|
{
|
|
// There is no lock information
|
|
lockState = LockState.createUnlocked(nodeRef);
|
|
}
|
|
}
|
|
|
|
// Never return a null LockState
|
|
Assert.notNull(lockState);
|
|
return lockState;
|
|
}
|
|
|
|
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
|
|
{
|
|
this.behaviourFilter = behaviourFilter;
|
|
}
|
|
|
|
public BehaviourFilter getBehaviourFilter()
|
|
{
|
|
return behaviourFilter;
|
|
}
|
|
|
|
public void setNodeIndexer(NodeIndexer nodeIndexer)
|
|
{
|
|
this.nodeIndexer = nodeIndexer;
|
|
}
|
|
|
|
@Override
|
|
public void flush()
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void beforeCommit(boolean readOnly)
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void beforeCompletion()
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void afterCommit()
|
|
{
|
|
}
|
|
|
|
@Override
|
|
public void afterRollback()
|
|
{
|
|
// As rollback has occurred we are unable to keep hold of any ephemeral locks set during this transaction.
|
|
Map<NodeRef, LockState> lockedNodes = TransactionalResourceHelper.getMap(KEY_MODIFIED_NODES);
|
|
for (LockState lockInfo : lockedNodes.values())
|
|
{
|
|
lockStore.set(lockInfo.getNodeRef(), lockInfo);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setEphemeralExpiryThreshold(int threshSecs)
|
|
{
|
|
ephemeralExpiryThreshold = threshSecs;
|
|
}
|
|
|
|
public int getEphemeralExpiryThreshold()
|
|
{
|
|
return ephemeralExpiryThreshold;
|
|
}
|
|
}
|