mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)
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
This commit is contained in:
@@ -431,6 +431,7 @@
|
|||||||
<property name="searchService" ref="admSearchService" />
|
<property name="searchService" ref="admSearchService" />
|
||||||
<property name="behaviourFilter" ref="policyBehaviourFilter" />
|
<property name="behaviourFilter" ref="policyBehaviourFilter" />
|
||||||
<property name="nodeIndexer" ref="nodeIndexer"/>
|
<property name="nodeIndexer" ref="nodeIndexer"/>
|
||||||
|
<property name="ephemeralExpiryThreshold" value="${alfresco.ephemeralLock.expiryThresh}"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
@@ -1076,6 +1076,10 @@
|
|||||||
<type>d:boolean</type>
|
<type>d:boolean</type>
|
||||||
<protected>true</protected>
|
<protected>true</protected>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="cm:lockAdditionalInfo">
|
||||||
|
<type>d:text</type>
|
||||||
|
<protected>true</protected>
|
||||||
|
</property>
|
||||||
</properties>
|
</properties>
|
||||||
</aspect>
|
</aspect>
|
||||||
|
|
||||||
|
@@ -1072,6 +1072,13 @@ alfresco.jmx.connector.enabled=true
|
|||||||
# Please, see MNT-11895 for details.
|
# Please, see MNT-11895 for details.
|
||||||
system.propval.uniquenessCheck.enabled=true
|
system.propval.uniquenessCheck.enabled=true
|
||||||
|
|
||||||
|
# Requests for ephemeral (in-memory) locks with expiry times (in seconds) greater
|
||||||
|
# than this value will result in persistent locks being created instead. By default
|
||||||
|
# this value is equal to the maximum allowed expiry for ephemeral locks, therefore
|
||||||
|
# this feature is disabled by default. Setting this to -1 would mean that ALL
|
||||||
|
# requests for ephemeral locks would result in persistent locks being created.
|
||||||
|
alfresco.ephemeralLock.expiryThresh=172800
|
||||||
|
|
||||||
# SurfConfigFolder Patch
|
# SurfConfigFolder Patch
|
||||||
#
|
#
|
||||||
# Do we defer running the surf-config folder patch?
|
# Do we defer running the surf-config folder patch?
|
||||||
|
@@ -104,6 +104,8 @@ public class LockServiceImpl implements LockService,
|
|||||||
|
|
||||||
private NodeIndexer nodeIndexer;
|
private NodeIndexer nodeIndexer;
|
||||||
|
|
||||||
|
private int ephemeralExpiryThreshold;
|
||||||
|
|
||||||
public void setNodeService(NodeService nodeService)
|
public void setNodeService(NodeService nodeService)
|
||||||
{
|
{
|
||||||
this.nodeService = nodeService;
|
this.nodeService = nodeService;
|
||||||
@@ -299,15 +301,16 @@ public class LockServiceImpl implements LockService,
|
|||||||
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire, Lifetime lifetime, String additionalInfo)
|
public void lock(NodeRef nodeRef, LockType lockType, int timeToExpire, Lifetime lifetime, String additionalInfo)
|
||||||
{
|
{
|
||||||
invokeBeforeLock(nodeRef, lockType);
|
invokeBeforeLock(nodeRef, lockType);
|
||||||
if (additionalInfo != null && !lifetime.equals(Lifetime.EPHEMERAL))
|
|
||||||
{
|
|
||||||
throw new IllegalArgumentException("additionalInfo may only be provided for ephemeral locks.");
|
|
||||||
}
|
|
||||||
if (lifetime.equals(Lifetime.EPHEMERAL) && (timeToExpire > MAX_EPHEMERAL_LOCK_SECONDS))
|
if (lifetime.equals(Lifetime.EPHEMERAL) && (timeToExpire > MAX_EPHEMERAL_LOCK_SECONDS))
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("Attempt to create ephemeral lock for " +
|
throw new IllegalArgumentException("Attempt to create ephemeral lock for " +
|
||||||
timeToExpire + " seconds - exceeds maximum allowed time.");
|
timeToExpire + " seconds - exceeds maximum allowed time.");
|
||||||
}
|
}
|
||||||
|
if (lifetime.equals(Lifetime.EPHEMERAL) && (timeToExpire > ephemeralExpiryThreshold))
|
||||||
|
{
|
||||||
|
lifetime = Lifetime.PERSISTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
nodeRef = tenantService.getName(nodeRef);
|
nodeRef = tenantService.getName(nodeRef);
|
||||||
|
|
||||||
@@ -344,7 +347,7 @@ public class LockServiceImpl implements LockService,
|
|||||||
{
|
{
|
||||||
// Add lock aspect if not already present
|
// Add lock aspect if not already present
|
||||||
ensureLockAspect(nodeRef);
|
ensureLockAspect(nodeRef);
|
||||||
persistLockProps(nodeRef, lockType, lifetime, userName, expiryDate);
|
persistLockProps(nodeRef, lockType, lifetime, userName, expiryDate, additionalInfo);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -372,7 +375,7 @@ public class LockServiceImpl implements LockService,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistLockProps(NodeRef nodeRef, LockType lockType, Lifetime lifetime, String userName, Date expiryDate)
|
private void persistLockProps(NodeRef nodeRef, LockType lockType, Lifetime lifetime, String userName, Date expiryDate, String additionalInfo)
|
||||||
{
|
{
|
||||||
addToIgnoreSet(nodeRef);
|
addToIgnoreSet(nodeRef);
|
||||||
try
|
try
|
||||||
@@ -382,6 +385,7 @@ public class LockServiceImpl implements LockService,
|
|||||||
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_TYPE, lockType.toString());
|
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_LOCK_LIFETIME, lifetime.toString());
|
||||||
this.nodeService.setProperty(nodeRef, ContentModel.PROP_EXPIRY_DATE, expiryDate);
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_EXPIRY_DATE, expiryDate);
|
||||||
|
this.nodeService.setProperty(nodeRef, ContentModel.PROP_LOCK_ADDITIONAL_INFO, additionalInfo);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -845,6 +849,7 @@ public class LockServiceImpl implements LockService,
|
|||||||
LockType lockType = lockTypeStr != null ? LockType.valueOf(lockTypeStr) : null;
|
LockType lockType = lockTypeStr != null ? LockType.valueOf(lockTypeStr) : null;
|
||||||
String lifetimeStr = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_LIFETIME);
|
String lifetimeStr = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_LIFETIME);
|
||||||
Lifetime lifetime = lifetimeStr != null ? Lifetime.valueOf(lifetimeStr) : null;
|
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!
|
// Mark lockstate as PERSISTENT as it was in the persistent storage!
|
||||||
lockState = LockState.createLock(
|
lockState = LockState.createLock(
|
||||||
@@ -853,7 +858,7 @@ public class LockServiceImpl implements LockService,
|
|||||||
lockOwner,
|
lockOwner,
|
||||||
expiryDate,
|
expiryDate,
|
||||||
lifetime,
|
lifetime,
|
||||||
null);
|
additionalInfo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -912,4 +917,15 @@ public class LockServiceImpl implements LockService,
|
|||||||
lockStore.set(lockInfo.getNodeRef(), lockInfo);
|
lockStore.set(lockInfo.getNodeRef(), lockInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEphemeralExpiryThreshold(int threshSecs)
|
||||||
|
{
|
||||||
|
ephemeralExpiryThreshold = threshSecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEphemeralExpiryThreshold()
|
||||||
|
{
|
||||||
|
return ephemeralExpiryThreshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,14 +19,12 @@
|
|||||||
package org.alfresco.service.cmr.lock;
|
package org.alfresco.service.cmr.lock;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.alfresco.api.AlfrescoPublicApi;
|
import org.alfresco.api.AlfrescoPublicApi;
|
||||||
import org.alfresco.repo.lock.mem.Lifetime;
|
import org.alfresco.repo.lock.mem.Lifetime;
|
||||||
import org.alfresco.repo.lock.mem.LockState;
|
import org.alfresco.repo.lock.mem.LockState;
|
||||||
import org.alfresco.service.Auditable;
|
import org.alfresco.service.Auditable;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -363,4 +361,13 @@ public interface LockService
|
|||||||
* @return LockState
|
* @return LockState
|
||||||
*/
|
*/
|
||||||
public LockState getLockState(NodeRef nodeRef);
|
public LockState getLockState(NodeRef nodeRef);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the maximum expiry time for which a request for an ephemeral lock
|
||||||
|
* will be honoured. Requests for ephemeral locks with expiry times greater than
|
||||||
|
* this value will be automatically converted to a request for a persistent lock.
|
||||||
|
*
|
||||||
|
* @param threshSecs
|
||||||
|
*/
|
||||||
|
public void setEphemeralExpiryThreshold(int threshSecs);
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.lock;
|
package org.alfresco.repo.lock;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotEquals;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -257,17 +259,12 @@ public class LockServiceImplTest extends BaseSpringTest
|
|||||||
this.lockService.lock(this.noAspectNode, LockType.WRITE_LOCK);
|
this.lockService.lock(this.noAspectNode, LockType.WRITE_LOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPersistentLockDisallowsAdditionalInfo()
|
public void testPersistentLockMayStoreAdditionalInfo()
|
||||||
{
|
{
|
||||||
try
|
lockService.lock(noAspectNode, LockType.NODE_LOCK, 0, Lifetime.PERSISTENT, "additional info");
|
||||||
{
|
|
||||||
lockService.lock(noAspectNode, LockType.NODE_LOCK, 0, Lifetime.PERSISTENT, "additional info");
|
LockState lockState = lockService.getLockState(noAspectNode);
|
||||||
fail("additionalInfo must be null for persistent lock, expected IllegalArgumentException.");
|
assertEquals("additional info", lockState.getAdditionalInfo());
|
||||||
}
|
|
||||||
catch (IllegalArgumentException e)
|
|
||||||
{
|
|
||||||
// Good, exception was thrown.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEphemeralLock()
|
public void testEphemeralLock()
|
||||||
@@ -818,6 +815,59 @@ public class LockServiceImplTest extends BaseSpringTest
|
|||||||
assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
|
assertEquals(LockStatus.LOCK_EXPIRED, this.lockService.getLockStatus(this.parentNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testEphemeralExpiryThreshold()
|
||||||
|
{
|
||||||
|
TestWithUserUtils.authenticateUser(GOOD_USER_NAME, PWD, rootNodeRef, this.authenticationService);
|
||||||
|
final int origThresh = ((LockServiceImpl)lockService).getEphemeralExpiryThreshold();
|
||||||
|
// Check the default situation is that the threshold does not apply.
|
||||||
|
assertEquals(LockServiceImpl.MAX_EPHEMERAL_LOCK_SECONDS, origThresh);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Set the ephemeral expiry threshold to a much smaller value than the default
|
||||||
|
// so that it takes effect.
|
||||||
|
lockService.setEphemeralExpiryThreshold(300);
|
||||||
|
|
||||||
|
// Check for an expiry time that should be unaffected by the threshold.
|
||||||
|
checkLifetimeForExpiry(Lifetime.EPHEMERAL, 0, Lifetime.EPHEMERAL);
|
||||||
|
checkLifetimeForExpiry(Lifetime.EPHEMERAL, 150, Lifetime.EPHEMERAL);
|
||||||
|
|
||||||
|
// Check the largest allowed ephemeral expiry time.
|
||||||
|
checkLifetimeForExpiry(Lifetime.EPHEMERAL, 300, Lifetime.EPHEMERAL);
|
||||||
|
|
||||||
|
// When the expiry is greater than the threshold, then the lock should be
|
||||||
|
// applied as a persistent lock.
|
||||||
|
checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL);
|
||||||
|
|
||||||
|
// Switch off ephemeral locks entirely
|
||||||
|
lockService.setEphemeralExpiryThreshold(-1);
|
||||||
|
// Always persistent...
|
||||||
|
checkLifetimeForExpiry(Lifetime.PERSISTENT, 0, Lifetime.EPHEMERAL);
|
||||||
|
checkLifetimeForExpiry(Lifetime.PERSISTENT, 150, Lifetime.EPHEMERAL);
|
||||||
|
checkLifetimeForExpiry(Lifetime.PERSISTENT, 300, Lifetime.EPHEMERAL);
|
||||||
|
checkLifetimeForExpiry(Lifetime.PERSISTENT, 301, Lifetime.EPHEMERAL);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lockService.setEphemeralExpiryThreshold(origThresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLifetimeForExpiry(Lifetime expectedLifetime, int expirySecs, Lifetime requestedLifetime)
|
||||||
|
{
|
||||||
|
lockService.unlock(parentNode);
|
||||||
|
assertNotEquals(LockStatus.LOCKED ,lockService.getLockStatus(parentNode));
|
||||||
|
lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, requestedLifetime);
|
||||||
|
LockState lock = lockService.getLockState(parentNode);
|
||||||
|
assertEquals(expectedLifetime, lock.getLifetime());
|
||||||
|
|
||||||
|
// Check that for any timeouts we test, a request for a persistent lock always yields a persistent lock.
|
||||||
|
lockService.unlock(parentNode);
|
||||||
|
assertNotEquals(LockStatus.LOCKED ,lockService.getLockStatus(parentNode));
|
||||||
|
lockService.lock(parentNode, LockType.WRITE_LOCK, expirySecs, Lifetime.PERSISTENT);
|
||||||
|
lock = lockService.getLockState(parentNode);
|
||||||
|
assertEquals(Lifetime.PERSISTENT, lock.getLifetime());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit test to validate the behaviour of creating children of locked nodes.
|
* Unit test to validate the behaviour of creating children of locked nodes.
|
||||||
* No lock - can create children
|
* No lock - can create children
|
||||||
|
@@ -210,6 +210,7 @@ public class LockableAspectInterceptorTest
|
|||||||
lockProps.put(ContentModel.PROP_LOCK_OWNER, "jbloggs");
|
lockProps.put(ContentModel.PROP_LOCK_OWNER, "jbloggs");
|
||||||
lockProps.put(ContentModel.PROP_LOCK_TYPE, LockType.READ_ONLY_LOCK.toString());
|
lockProps.put(ContentModel.PROP_LOCK_TYPE, LockType.READ_ONLY_LOCK.toString());
|
||||||
lockProps.put(ContentModel.PROP_EXPIRY_DATE, now);
|
lockProps.put(ContentModel.PROP_EXPIRY_DATE, now);
|
||||||
|
lockProps.put(ContentModel.PROP_LOCK_ADDITIONAL_INFO, "{ \"fieldName\": \"extra lock info\" }");
|
||||||
nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, lockProps);
|
nodeService.addAspect(nodeRef, ContentModel.ASPECT_LOCKABLE, lockProps);
|
||||||
|
|
||||||
Map<QName, Serializable> readProps = nodeService.getProperties(nodeRef);
|
Map<QName, Serializable> readProps = nodeService.getProperties(nodeRef);
|
||||||
@@ -221,6 +222,7 @@ public class LockableAspectInterceptorTest
|
|||||||
assertEquals(Lifetime.PERSISTENT.toString(), readProps.get(ContentModel.PROP_LOCK_LIFETIME));
|
assertEquals(Lifetime.PERSISTENT.toString(), readProps.get(ContentModel.PROP_LOCK_LIFETIME));
|
||||||
// Double check - not really present
|
// Double check - not really present
|
||||||
ensurePropertyNotPresent(nodeRef, ContentModel.PROP_LOCK_LIFETIME);
|
ensurePropertyNotPresent(nodeRef, ContentModel.PROP_LOCK_LIFETIME);
|
||||||
|
assertEquals("{ \"fieldName\": \"extra lock info\" }", readProps.get(ContentModel.PROP_LOCK_ADDITIONAL_INFO));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user