Merged BRANCHES/DEV/mward/head_locktry_fix to HEAD:

55423: ALF-20031: LockStore is no longer used as a cache for persistent locks - it only holds Lifetime.EPHEMERAL locks.
   55458: ALF-20031: added repeatable read within transactions to LockStores
   55461: ALF-20031: moved transactional test code into separate test classes to avoid clutter.
   55470: ALF-20031: added test for LockStore reads when no transaction is available.
   55473: Generated LockState.toString() to aid debugging, stack trace clarity etc.
   55481: ALF-20031: added tests for null value behaviour (transactional repeatable reads).
   55663: ALF-20031: (work in progress, broken tests) removing concurrency locks from AbstractLockStore.
   55664: ALF-20031: moved inner Thread classes to anonymous inner classes as there will be more of these coming.
   55675: ALF-20031: fixed AbstractLockStoreTxTest
   55681: ALF-20031: added more test cases to AbstractLockStoreTxTest
   55683: Added missing tests to enterprise.repo.cluster package's BuildSafeTestSuite.
   55684: ALF-20031: Fix HazelcastLockStoreTest to clear lock store backing map in createLockStore() fixture setup.
   55688: Commented LockStore.clear() as a DO NOT USE method.
   55694: ALF-20031: removed LockStore.contains() as this is not required, and was currently unsafe (no repeatable reads).
   55696: ALF-20031: Fix AbstractLockStore.clear(): was not clearing transactionally scoped map.
   55700: ALF-20031: removed concurrency locks from LockServiceImpl.getLockState(NodeRef)
   55712: ALF-20031: removed concurrency lock from LockServiceImpl.lock()
   55716: ALF-20031: removed lockstore locking from LockableAspectInterceptor.
   55718: ALF-20031: renamed method to isEphemeralLock()
   55719: ALF-20031: removed concurrency lock from LockServiceImpl.unlock()
   55728: ALF-20031: added cm:lockLifetime property to content model, and added persistence within lockservice.
   55732: ALF-20031: LockableAspectInterceptor no longer uses ephemeral lockstore for setProperties() handling.
   55753: ALF-20031: upon tx rollback, ephemeral locks are now rolled back to the last known value, rather than always being unlocked.
   55759: ALF-20031: temporary fix, AbstractLockStoreTxTest needs further attention for thread coordination.
   55760: ALF-20091: disabled spawning of new transactions in LockKeeperImpl.refreshAllLocks()
   55767: ALF-20031: changed AbstractLockStore to use getFullyAuthenticatedUser() instead of getRunAsUser().
   55833: ALF-20091: reverted LockKeeperImpl to previous (create new tx) behaviour. Altered test to use separate transactions per lock phase.
   55855: ALF-20031: fixed ReplicationServiceIntegrationTest, LockableAspectInterceptor's handling of cm:lockLifetime.



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@55893 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Matt Ward
2013-09-24 12:14:46 +00:00
parent 50bf8c4948
commit 4055228bdb
15 changed files with 1063 additions and 353 deletions

View File

@@ -68,6 +68,7 @@ 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;
@@ -88,7 +89,7 @@ public class LockServiceImpl implements LockService,
/** 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_LOCKED_NODES = "lockService.lockedNode";
private static final Object KEY_MODIFIED_NODES = "lockService.lockedNode";
private NodeService nodeService;
private TenantService tenantService;
@@ -318,7 +319,11 @@ public class LockServiceImpl implements LockService,
lockType = LockType.WRITE_LOCK;
}
LockStatus currentLockStatus = getLockStatus(nodeRef, userName);
// 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
@@ -328,50 +333,43 @@ public class LockServiceImpl implements LockService,
LockStatus.LOCK_EXPIRED.equals(currentLockStatus) == true ||
LockStatus.LOCK_OWNER.equals(currentLockStatus) == true)
{
lockStore.acquireConcurrencyLock(nodeRef);
try
final Date expiryDate = makeExpiryDate(timeToExpire);
// Store the lock in the appropriate place.
if (lifetime == Lifetime.PERSISTENT)
{
// Double check lock status now that the lockStore has been locked by NodeRef
if (getLockStatus(nodeRef, userName) == LockStatus.LOCKED)
{
throw new UnableToAquireLockException(nodeRef);
}
final Date expiryDate = makeExpiryDate(timeToExpire);
// Only persist the lock if required.
if (lifetime.equals(Lifetime.PERSISTENT))
lockableAspectInterceptor.disableForThread();
try
{
// Add lock aspect if not already present
lockableAspectInterceptor.disableForThread();
try
{
ensureLockAspect(nodeRef);
persistLockProps(nodeRef, lockType, userName, expiryDate);
}
finally
{
lockableAspectInterceptor.enableForThread();
}
ensureLockAspect(nodeRef);
persistLockProps(nodeRef, lockType, lifetime, userName, expiryDate);
}
// Always store the lock in memory.
lockStore.set(
nodeRef,
LockState.createLock(nodeRef, lockType, userName, expiryDate, lifetime, additionalInfo));
// Record the NodeRef being locked, so that it can be removed on rollback.
TransactionalResourceHelper.getSet(KEY_LOCKED_NODES).add(nodeRef);
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);
}
finally
else
{
lockStore.releaseConcurrencyLock(nodeRef);
throw new IllegalStateException(lifetime.getClass().getSimpleName() +
" is not a valid value: " + lifetime.toString());
}
}
}
private void persistLockProps(NodeRef nodeRef, LockType lockType, String userName, Date expiryDate)
private void persistLockProps(NodeRef nodeRef, LockType lockType, Lifetime lifetime, String userName, Date expiryDate)
{
addToIgnoreSet(nodeRef);
try
@@ -379,6 +377,7 @@ public class LockServiceImpl implements LockService,
// 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);
}
finally
@@ -467,48 +466,47 @@ public class LockServiceImpl implements LockService,
// Unlock the parent
nodeRef = tenantService.getName(nodeRef);
lockStore.acquireConcurrencyLock(nodeRef);
try
LockState lockState = getLockState(nodeRef);
if (lockState.isLockInfo())
{
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))
{
// 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);
}
throw new UnableToReleaseLockException(nodeRef, CAUSE.CHECKED_OUT);
}
// Always unlock in memory regardless of lifetime
lockStore.set(nodeRef, LockState.createUnlocked(nodeRef));
// Remove the lock from persistent storage.
if (lockState.getLifetime().equals(Lifetime.PERSISTENT))
// 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
{
addToIgnoreSet(nodeRef);
behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE);
lockableAspectInterceptor.disableForThread();
try
// Clear the lock
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
{
// 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);
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));
}
else
{
throw new IllegalStateException("Unhandled Lifetime value: " + lifetime);
}
}
finally
{
lockStore.releaseConcurrencyLock(nodeRef);
}
if (unlockChildren)
@@ -552,50 +550,22 @@ public class LockServiceImpl implements LockService,
* @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 = lockStatus(userName, lockOwner, expiryDate);
return status;
LockStatus status = LockUtils.lockStatus(userName, lockOwner, expiryDate);
return new Pair<LockState, LockStatus>(lockState, status);
}
/**
* Given the lock owner and expiry date of a lock calculates the lock status with respect
* to the user name supplied, e.g. the current user.
*
* @param userName User name to evaluate the lock against.
* @param lockOwner Owner of the lock.
* @param expiryDate Expiry date of the lock.
* @return LockStatus
*/
private LockStatus lockStatus(String userName, String lockOwner, Date expiryDate)
{
LockStatus result = LockStatus.NO_LOCK;
if (lockOwner != null)
{
if (expiryDate != null && expiryDate.before(new Date()) == true)
{
// Indicate that the lock has expired
result = LockStatus.LOCK_EXPIRED;
}
else
{
if (lockOwner.equals(userName) == true)
{
result = LockStatus.LOCK_OWNER;
}
else
{
result = LockStatus.LOCKED;
}
}
}
return result;
}
/**
* @see LockService#getLockType(NodeRef)
*/
@@ -850,52 +820,35 @@ public class LockServiceImpl implements LockService,
@Override
public LockState getLockState(NodeRef nodeRef)
{
// Check in-memory for ephemeral locks first.
LockState lockState = lockStore.get(nodeRef);
if (lockState == null)
{
lockStore.acquireConcurrencyLock(nodeRef);
try
// No in-memory state, so get from the DB.
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
{
// Double check there is still no lock
if (lockStore.contains(nodeRef))
{
// In-memory lock state has appeared since last check, so use it.
lockState = lockStore.get(nodeRef);
}
else
{
// Still no in-memory state, so get from the DB and cache it also.
lockableAspectInterceptor.disableForThread();
if (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;
// Add to memory store, we mark it as PERSISTENT as it was in the persistent storage!
lockState = LockState.createLock(
nodeRef,
lockType,
lockOwner,
expiryDate,
Lifetime.PERSISTENT,
null);
}
else
{
// There is no lock information
lockState = LockState.createUnlocked(nodeRef);
}
// Cache the lock state
lockStore.set(nodeRef, lockState);
}
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;
// Mark lockstate as PERSISTENT as it was in the persistent storage!
lockState = LockState.createLock(
nodeRef,
lockType,
lockOwner,
expiryDate,
lifetime,
null);
}
finally
else
{
lockableAspectInterceptor.enableForThread();
lockStore.releaseConcurrencyLock(nodeRef);
// There is no lock information
lockState = LockState.createUnlocked(nodeRef);
}
}
@@ -937,11 +890,11 @@ public class LockServiceImpl implements LockService,
@Override
public void afterRollback()
{
// As rollback has occurred we are unable to keep hold of any locks set during this transaction.
Set<NodeRef> lockedNodes = TransactionalResourceHelper.getSet(KEY_LOCKED_NODES);
for (NodeRef nodeRef : lockedNodes)
// 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(nodeRef, LockState.createUnlocked(nodeRef));
lockStore.set(lockInfo.getNodeRef(), lockInfo);
}
}
}

View File

@@ -18,6 +18,8 @@
*/
package org.alfresco.repo.lock;
import java.util.Date;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.lock.LockType;
@@ -67,4 +69,39 @@ public class LockUtils
return true;
}
}
/**
* Given the lock owner and expiry date of a lock calculates the lock status with respect
* to the user name supplied, e.g. the current user.
*
* @param userName User name to evaluate the lock against.
* @param lockOwner Owner of the lock.
* @param expiryDate Expiry date of the lock.
* @return LockStatus
*/
public static LockStatus lockStatus(String userName, String lockOwner, Date expiryDate)
{
LockStatus result = LockStatus.NO_LOCK;
if (lockOwner != null)
{
if (expiryDate != null && expiryDate.before(new Date()) == true)
{
// Indicate that the lock has expired
result = LockStatus.LOCK_EXPIRED;
}
else
{
if (lockOwner.equals(userName) == true)
{
result = LockStatus.LOCK_OWNER;
}
else
{
result = LockStatus.LOCKED;
}
}
}
return result;
}
}

View File

@@ -18,10 +18,19 @@
*/
package org.alfresco.repo.lock.mem;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.alfresco.repo.lock.LockUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.lock.UnableToAquireLockException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* Base class for LockStore implementations that use a ConcurrentMap as storage.
@@ -55,54 +64,101 @@ public abstract class AbstractLockStore<T extends ConcurrentMap<NodeRef, LockSta
@Override
public LockState get(NodeRef nodeRef)
{
// Always lock on NodeRef related LockStore operations. Otherwise it is possible
// to, for example, get LockState data that is not consistent with the props in persistent storage.
acquireConcurrencyLock(nodeRef);
try
LockState lockState;
Map<NodeRef, LockState> txMap = getTxMap();
if (txMap != null && txMap.containsKey(nodeRef))
{
return map.get(nodeRef);
// The transactional map is able to provide the LockState
lockState = txMap.get(nodeRef);
}
finally
else
{
releaseConcurrencyLock(nodeRef);
}
}
@Override
public boolean contains(NodeRef nodeRef)
{
acquireConcurrencyLock(nodeRef);
try
{
return map.containsKey(nodeRef);
}
finally
{
releaseConcurrencyLock(nodeRef);
lockState = map.get(nodeRef);
if (txMap != null)
{
// As the txMap doesn't have the LockState, cache it for later.
txMap.put(nodeRef, lockState);
}
}
return lockState;
}
@Override
public void set(NodeRef nodeRef, LockState lockState)
{
acquireConcurrencyLock(nodeRef);
try
Map<NodeRef, LockState> txMap = getTxMap();
LockState previousLockState = null;
if (txMap != null)
{
doSet(nodeRef, lockState);
if (txMap.containsKey(nodeRef))
{
// There is known previous state.
previousLockState = txMap.get(nodeRef);
}
else
{
// No previous known state - get the current state, this becomes
// the previous known state.
previousLockState = get(nodeRef);
}
}
finally
else
{
releaseConcurrencyLock(nodeRef);
// No transaction, but we still need to know the previous state, before attempting
// to set new state.
previousLockState = get(nodeRef);
}
// Has the lock been succesfully placed into the lock store?
boolean updated = false;
if (previousLockState != null)
{
String userName = AuthenticationUtil.getFullyAuthenticatedUser();
String owner = previousLockState.getOwner();
Date expires = previousLockState.getExpires();
if (LockUtils.lockStatus(userName, owner, expires) == LockStatus.LOCKED)
{
throw new UnableToAquireLockException(nodeRef);
}
// Use ConcurrentMap.replace(key, old, new) so that we can ensure we don't encounter a
// 'lost update' (i.e. someone else has locked a node while we were thinking about it).
updated = map.replace(nodeRef, previousLockState, lockState);
}
else
{
if (map.putIfAbsent(nodeRef, lockState) == null)
{
updated = true;
}
}
if (!updated)
{
String msg = String.format("Attempt to update lock state failed, old=%s, new=%s, noderef=%s",
previousLockState, lockState, nodeRef);
throw new ConcurrencyFailureException(msg);
}
else
{
// Keep the new value for future reads within this TX.
if (txMap != null)
{
txMap.put(nodeRef, lockState);
}
}
}
protected abstract void doSet(NodeRef nodeRef, LockState lockState);
@Override
public void clear()
{
// TODO: lock whole map?
map.clear();
Map<NodeRef, LockState> txMap = getTxMap();
if (txMap != null)
{
txMap.clear();
}
}
@Override
@@ -111,10 +167,26 @@ public abstract class AbstractLockStore<T extends ConcurrentMap<NodeRef, LockSta
@Override
public abstract void releaseConcurrencyLock(NodeRef nodeRef);
/**
* Returns a transactionally scoped Map that is used to provide repeatable lock store queries
* for a given NodeRef. If no transaction is present, then null is returned.
*
* @return Transactional Map or null if not available.
*/
protected Map<NodeRef, LockState> getTxMap()
{
if (!TransactionSynchronizationManager.isSynchronizationActive())
{
return null;
}
Map<NodeRef, LockState> map = TransactionalResourceHelper.getMap(getClass().getName()+".repeatableReadMap");
return map;
}
@Override
public Set<NodeRef> getNodes()
{
// TODO: lock whole map?
return map.keySet();
}
}

View File

@@ -215,4 +215,12 @@ public final class LockState implements Serializable
else if (!this.owner.equals(other.owner)) return false;
return true;
}
@Override
public String toString()
{
return "LockState [nodeRef=" + this.nodeRef + ", lockType=" + this.lockType + ", owner="
+ this.owner + ", expires=" + this.expires + ", lifetime=" + this.lifetime
+ ", additionalInfo=" + this.additionalInfo + "]";
}
}

View File

@@ -51,11 +51,16 @@ import org.alfresco.service.cmr.repository.NodeRef;
public interface LockStore
{
LockState get(NodeRef nodeRef);
boolean contains(NodeRef nodeRef);
void set(NodeRef nodeRef, LockState lockState);
void clear();
void acquireConcurrencyLock(NodeRef nodeRef);
void releaseConcurrencyLock(NodeRef nodeRef);
void setMaxTryLockMillis(long maxTryLockMillis);
public Set<NodeRef> getNodes();
/**
* WARNING: only use in test code - unsafe method for production use.
*
* TODO: remove this method?
*/
void clear();
}

View File

@@ -106,10 +106,4 @@ public class LockStoreImpl extends AbstractLockStore<ConcurrentMap<NodeRef, Lock
WriteLock writeLock = rwLock.writeLock();
return writeLock;
}
@Override
protected void doSet(NodeRef nodeRef, LockState lockState)
{
map.put(nodeRef, lockState);
}
}

View File

@@ -87,7 +87,8 @@ public class LockableAspectInterceptor implements MethodInterceptor
// If the hasAspect() call is checking for cm:lockable and this is an ephemeral lock,
// then spoof the aspect's existence on the node.
if (ContentModel.ASPECT_LOCKABLE.equals(aspectTypeQName) && hasEphemeralLock(nodeRef))
LockState lockState = lockStore.get(nodeRef);
if (ContentModel.ASPECT_LOCKABLE.equals(aspectTypeQName) && isEphemeralLock(lockState))
{
return true;
}
@@ -97,7 +98,8 @@ public class LockableAspectInterceptor implements MethodInterceptor
{
NodeRef nodeRef = (NodeRef) args[0];
Set<QName> aspects = (Set<QName>) invocation.proceed();
if (hasEphemeralLock(nodeRef) && !aspects.contains(ContentModel.ASPECT_LOCKABLE))
LockState lockState = lockStore.get(nodeRef);
if (isEphemeralLock(lockState) && !aspects.contains(ContentModel.ASPECT_LOCKABLE))
{
aspects.add(ContentModel.ASPECT_LOCKABLE);
}
@@ -106,24 +108,26 @@ public class LockableAspectInterceptor implements MethodInterceptor
else if (methodName.equals("getProperties"))
{
NodeRef nodeRef = (NodeRef) args[0];
lockStore.acquireConcurrencyLock(nodeRef);
try
Map<QName, Serializable> properties = (Map<QName, Serializable>) invocation.proceed();
LockState lockState = lockStore.get(nodeRef);
if (isEphemeralLock(lockState))
{
Map<QName, Serializable> properties = (Map<QName, Serializable>) invocation.proceed();
if (hasEphemeralLock(nodeRef))
String userName = lockState.getOwner();
properties.put(ContentModel.PROP_LOCK_OWNER, userName);
properties.put(ContentModel.PROP_LOCK_TYPE, lockState.getLockType().toString());
properties.put(ContentModel.PROP_EXPIRY_DATE, lockState.getExpires());
properties.put(ContentModel.PROP_LOCK_LIFETIME, Lifetime.EPHEMERAL);
}
else if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
{
// Persistent lock, ensure lifetime property is present.
if (!properties.containsKey(ContentModel.PROP_LOCK_LIFETIME))
{
LockState lockState = lockStore.get(nodeRef);
String userName = lockState.getOwner();
properties.put(ContentModel.PROP_LOCK_OWNER, userName);
properties.put(ContentModel.PROP_LOCK_TYPE, lockState.getLockType().toString());
properties.put(ContentModel.PROP_EXPIRY_DATE, lockState.getExpires());
properties.put(ContentModel.PROP_LOCK_LIFETIME, Lifetime.PERSISTENT);
}
return properties;
}
finally
{
lockStore.releaseConcurrencyLock(nodeRef);
}
return properties;
}
else if (methodName.equals("getProperty"))
{
@@ -133,60 +137,61 @@ public class LockableAspectInterceptor implements MethodInterceptor
// Avoid locking unless it is an interesting property.
if (isLockProperty(propQName))
{
lockStore.acquireConcurrencyLock(nodeRef);
try
LockState lockState = lockStore.get(nodeRef);
if (isEphemeralLock(lockState))
{
if (hasEphemeralLock(nodeRef))
if (ContentModel.PROP_LOCK_OWNER.equals(propQName))
{
LockState lockState = lockStore.get(nodeRef);
if (ContentModel.PROP_LOCK_OWNER.equals(propQName))
{
return lockState.getOwner();
}
else if (ContentModel.PROP_LOCK_TYPE.equals(propQName))
{
return lockState.getLockType().toString();
}
else if (ContentModel.PROP_EXPIRY_DATE.equals(propQName))
{
return lockState.getExpires();
}
return lockState.getOwner();
}
else if (ContentModel.PROP_LOCK_TYPE.equals(propQName))
{
return lockState.getLockType().toString();
}
else if (ContentModel.PROP_EXPIRY_DATE.equals(propQName))
{
return lockState.getExpires();
}
else if (ContentModel.PROP_LOCK_LIFETIME.equals(propQName))
{
return lockState.getLifetime().toString();
}
}
finally
else if (ContentModel.PROP_LOCK_LIFETIME.equals(propQName))
{
lockStore.releaseConcurrencyLock(nodeRef);
// Is there a persistent lock?
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE))
{
return Lifetime.PERSISTENT.toString();
}
}
}
}
return invocation.proceed();
}
else if (methodName.equals("setProperties"))
{
// If a client has retrieved the node's properties using getProperties and is saving them
// back using setProperties then it is important that lock properties (e.g. cm:lockType, cm:lockOwner)
// are not persisted if there is an ephemeral lock present - otherwise the ephemeral lock will
// be effectively converted into a peristent lock.
// Ephemeral locks must not be persisted to the database.
// TODO: This is potentially creating an ephemeral lock here, put it in the lockstore?
NodeRef nodeRef = (NodeRef) args[0];
Map<QName, Serializable> newProperties = (Map<QName, Serializable>) args[1];
lockStore.acquireConcurrencyLock(nodeRef);
try
if (newProperties.get(ContentModel.PROP_LOCK_LIFETIME) == Lifetime.EPHEMERAL)
{
if (hasEphemeralLock(nodeRef) && containsLockProperty(newProperties))
{
Map<QName, Serializable> convertedProperties = filterLockProperties(newProperties);
// Now complete the call by passing the converted properties
nodeService.setProperties(nodeRef, convertedProperties);
return null;
}
else
{
return invocation.proceed();
}
Map<QName, Serializable> convertedProperties = filterLockProperties(newProperties);
// Now complete the call by passing the converted properties
nodeService.setProperties(nodeRef, convertedProperties);
return null;
}
finally
else if (newProperties.containsKey(ContentModel.PROP_LOCK_LIFETIME))
{
lockStore.releaseConcurrencyLock(nodeRef);
// Always remove this property, even for persistent locks.
newProperties.remove(ContentModel.PROP_LOCK_LIFETIME);
nodeService.setProperties(nodeRef, newProperties);
return null;
}
else
{
return invocation.proceed();
}
}
else
@@ -235,19 +240,6 @@ public class LockableAspectInterceptor implements MethodInterceptor
return filteredProps;
}
/**
* Does the collection contain a lock related property?
*/
private boolean containsLockProperty(Map<QName, ?> properties)
{
boolean containsLockProperty = (
properties.containsKey(ContentModel.PROP_LOCK_OWNER) ||
properties.containsKey(ContentModel.PROP_LOCK_TYPE) ||
properties.containsKey(ContentModel.PROP_EXPIRY_DATE)
);
return containsLockProperty;
}
/**
* Return true if the specified property QName is for a lock-related property.
*/
@@ -256,13 +248,13 @@ public class LockableAspectInterceptor implements MethodInterceptor
boolean isLockProp =
propQName.equals(ContentModel.PROP_LOCK_OWNER) ||
propQName.equals(ContentModel.PROP_LOCK_TYPE) ||
propQName.equals(ContentModel.PROP_LOCK_LIFETIME) ||
propQName.equals(ContentModel.PROP_EXPIRY_DATE);
return isLockProp;
}
private boolean hasEphemeralLock(NodeRef nodeRef)
private boolean isEphemeralLock(LockState lockState)
{
LockState lockState = lockStore.get(nodeRef);
boolean ephemeral = lockState != null &&
lockState.isLockInfo() &&
lockState.getLifetime() == Lifetime.EPHEMERAL;