mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
MOB-434: Cluster-wide Locking Service
- DAO and unit tests (MOB-436) - MySQL and Derby scripts (MOB-438) - TODO: Oracle, PostgreSQL and SQLServer for Enterprise - TOD0: Wrap lock wait and lock retry behaviour git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@13947 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -73,6 +73,7 @@
|
||||
<value>classpath:alfresco/dbscripts/create/2.2/${db.script.dialect}/AlfrescoPostCreate-2.2-Extra.sql</value>
|
||||
<value>classpath:alfresco/dbscripts/create/2.2/${db.script.dialect}/post-create-indexes-04.sql</value>
|
||||
<value>classpath:alfresco/dbscripts/create/3.0/${db.script.dialect}/create-activities-extras.sql</value>
|
||||
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-LockTables.sql</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="validateUpdateScriptPatches">
|
||||
@@ -90,6 +91,7 @@
|
||||
<ref bean="patch.db-V2.2-Upgrade-From-2.1" />
|
||||
<ref bean="patch.db-V2.2-Upgrade-From-2.2SP1" />
|
||||
<ref bean="patch.db-V2.2-Person-2" />
|
||||
<ref bean="patch.db-V3.2-LockTables" />
|
||||
</list>
|
||||
</property>
|
||||
<property name="postUpdateScriptPatches">
|
||||
|
@@ -0,0 +1,46 @@
|
||||
--
|
||||
-- Title: Create lock tables
|
||||
-- Database: Derby
|
||||
-- Since: V3.2 Schema 2011
|
||||
-- Author: Derek Hulley
|
||||
--
|
||||
-- Please contact support@alfresco.com if you need assistance with the upgrade.
|
||||
--
|
||||
|
||||
CREATE TABLE alf_lock_resource
|
||||
(
|
||||
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
|
||||
version BIGINT NOT NULL,
|
||||
qname_ns_id BIGINT NOT NULL,
|
||||
qname_localname VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT fk_alf_lockr_ns FOREIGN KEY (qname_ns_id) REFERENCES alf_namespace (id),
|
||||
CONSTRAINT fk_alf_lockr_id PRIMARY KEY (id),
|
||||
CONSTRAINT idx_alf_lockr_key UNIQUE (qname_ns_id, qname_localname)
|
||||
);
|
||||
|
||||
CREATE TABLE alf_lock
|
||||
(
|
||||
id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
|
||||
version BIGINT NOT NULL,
|
||||
shared_resource_id BIGINT NOT NULL,
|
||||
excl_resource_id BIGINT NOT NULL,
|
||||
lock_token VARCHAR(36) NOT NULL,
|
||||
start_time BIGINT NOT NULL,
|
||||
expiry_time BIGINT NOT NULL,
|
||||
CONSTRAINT fk_alf_lock_shared FOREIGN KEY (shared_resource_id) REFERENCES alf_lock_resource (id),
|
||||
CONSTRAINT fk_alf_lock_excl FOREIGN KEY (excl_resource_id) REFERENCES alf_lock_resource (id),
|
||||
CONSTRAINT fk_alf_lock_id PRIMARY KEY (id),
|
||||
CONSTRAINT idx_alf_lock_key UNIQUE (shared_resource_id, excl_resource_id)
|
||||
);
|
||||
|
||||
--
|
||||
-- Record script finish
|
||||
--
|
||||
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.2-LockTables';
|
||||
INSERT INTO alf_applied_patch
|
||||
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
|
||||
VALUES
|
||||
(
|
||||
'patch.db-V3.2-LockTables', 'Manually executed script upgrade V3.2: Lock Tables',
|
||||
0, 2010, -1, 2011, null, 'UNKOWN', 1, 1, 'Script completed'
|
||||
);
|
@@ -0,0 +1,46 @@
|
||||
--
|
||||
-- Title: Create lock tables
|
||||
-- Database: MySQL InnoDB
|
||||
-- Since: V3.2 Schema 2011
|
||||
-- Author: Derek Hulley
|
||||
--
|
||||
-- Please contact support@alfresco.com if you need assistance with the upgrade.
|
||||
--
|
||||
|
||||
CREATE TABLE alf_lock_resource
|
||||
(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
version BIGINT NOT NULL,
|
||||
qname_ns_id BIGINT NOT NULL,
|
||||
qname_localname VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT fk_alf_lockr_ns FOREIGN KEY (qname_ns_id) REFERENCES alf_namespace (id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX idx_alf_lockr_key (qname_ns_id, qname_localname)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
CREATE TABLE alf_lock
|
||||
(
|
||||
id BIGINT NOT NULL auto_increment,
|
||||
version BIGINT NOT NULL,
|
||||
shared_resource_id BIGINT NOT NULL,
|
||||
excl_resource_id BIGINT NOT NULL,
|
||||
lock_token VARCHAR(36) NOT NULL,
|
||||
start_time BIGINT NOT NULL,
|
||||
expiry_time BIGINT NOT NULL,
|
||||
CONSTRAINT fk_alf_lock_shared FOREIGN KEY (shared_resource_id) REFERENCES alf_lock_resource (id),
|
||||
CONSTRAINT fk_alf_lock_excl FOREIGN KEY fk_alf_lock_excl (excl_resource_id) REFERENCES alf_lock_resource (id),
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX idx_alf_lock_key (shared_resource_id, excl_resource_id)
|
||||
) TYPE=InnoDB;
|
||||
|
||||
--
|
||||
-- Record script finish
|
||||
--
|
||||
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V3.2-LockTables';
|
||||
INSERT INTO alf_applied_patch
|
||||
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
|
||||
VALUES
|
||||
(
|
||||
'patch.db-V3.2-LockTables', 'Manually executed script upgrade V3.2: Lock Tables',
|
||||
0, 2010, -1, 2011, null, 'UNKOWN', 1, 1, 'Script completed'
|
||||
);
|
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<!DOCTYPE sqlMap
|
||||
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
|
||||
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
|
||||
|
||||
<sqlMap namespace="alfresco.lock">
|
||||
|
||||
<insert id="insert.LockResource" parameterClass="LockResource" >
|
||||
<include refid="insert.LockResource.AutoIncrement"/>
|
||||
<selectKey resultClass="long" keyProperty="id" type="post">
|
||||
values IDENTITY_VAL_LOCAL()
|
||||
</selectKey>
|
||||
</insert>
|
||||
|
||||
<insert id="insert.Lock" parameterClass="Lock" >
|
||||
<include refid="insert.Lock.AutoIncrement"/>
|
||||
<selectKey resultClass="long" keyProperty="id" type="post">
|
||||
values IDENTITY_VAL_LOCAL()
|
||||
</selectKey>
|
||||
</insert>
|
||||
|
||||
</sqlMap>
|
@@ -19,17 +19,32 @@
|
||||
|
||||
<resultMap id="result.LockResource" class="LockResource">
|
||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="version" column="version" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="qnameNamespaceId" column="qname_ns_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="qnameLocalName" column="qname_localname" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="result.Lock" class="Lock">
|
||||
<result property="id" column="id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="version" column="version" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="sharedResourceId" column="shared_resource_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="exclusiveResourceId" column="excl_resource_id" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="lockHolder" column="lock_holder" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<result property="lockToken" column="lock_token" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<result property="startTime" column="start_time" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<result property="expiryTime" column="expiry_time" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- -->
|
||||
<!-- Parameter Maps -->
|
||||
<!-- -->
|
||||
|
||||
<parameterMap id="parameter.ExclusiveLockUpdateMap" class="map">
|
||||
<parameter property="newLockToken" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
<parameter property="newExpiryTime" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<parameter property="exclusiveLockResourceId" jdbcType="BIGINT" javaType="java.lang.Long"/>
|
||||
<parameter property="oldLockToken" jdbcType="VARCHAR" javaType="java.lang.String"/>
|
||||
</parameterMap>
|
||||
|
||||
<!-- -->
|
||||
<!-- SQL Snippets -->
|
||||
<!-- -->
|
||||
@@ -40,15 +55,15 @@
|
||||
</sql>
|
||||
|
||||
<sql id="insert.Lock.AutoIncrement">
|
||||
insert into alf_lock (version, shared_resource_id, excl_resource_id, lock_holder)
|
||||
values (#version#, #sharedResourceId#, #exclusiveResourceId#, lower(#lockHolder#))
|
||||
insert into alf_lock (version, shared_resource_id, excl_resource_id, lock_token, start_time, expiry_time)
|
||||
values (#version#, #sharedResourceId#, #exclusiveResourceId#, lower(#lockToken#), #startTime#, #expiryTime#)
|
||||
</sql>
|
||||
|
||||
<!-- -->
|
||||
<!-- Statements -->
|
||||
<!-- -->
|
||||
|
||||
<!-- Get the lock resource entity that is referenced by the locks -->
|
||||
<!-- Get the lock resource entity by qualified name -->
|
||||
<select id="select.LockResourceByQName" parameterClass="LockResource" resultMap="result.LockResource">
|
||||
select
|
||||
*
|
||||
@@ -59,6 +74,27 @@
|
||||
qname_localname = lower(#qnameLocalName#)
|
||||
</select>
|
||||
|
||||
<!-- Get a lock based on the ID -->
|
||||
<select id="select.LockByID" parameterClass="Lock" resultMap="result.Lock">
|
||||
select
|
||||
*
|
||||
from
|
||||
alf_lock
|
||||
where
|
||||
id = #id#
|
||||
</select>
|
||||
|
||||
<!-- Get a lock based on the unique key (shared and exclusive lock IDs) -->
|
||||
<select id="select.LockByKey" parameterClass="Lock" resultMap="result.Lock">
|
||||
select
|
||||
*
|
||||
from
|
||||
alf_lock
|
||||
where
|
||||
shared_resource_id = #sharedResourceId# and
|
||||
excl_resource_id = #exclusiveResourceId#
|
||||
</select>
|
||||
|
||||
<!-- Get all locks for a given list of resource IDs -->
|
||||
<select id="select.LockBySharedIds" resultMap="result.Lock">
|
||||
select
|
||||
@@ -72,6 +108,33 @@
|
||||
#[]#
|
||||
</iterate>
|
||||
</dynamic>
|
||||
</select>
|
||||
</select>
|
||||
|
||||
<!-- Optimistic update of the lock -->
|
||||
<update id="update.Lock" parameterClass="Lock">
|
||||
update
|
||||
alf_lock
|
||||
set
|
||||
version = #version#,
|
||||
lock_token = lower(#lockToken#),
|
||||
start_time = #startTime#,
|
||||
expiry_time = #expiryTime#
|
||||
where
|
||||
id = #id# and
|
||||
version = (#version# -1)
|
||||
</update>
|
||||
|
||||
<!-- Update locks for a given exclusive lock -->
|
||||
<update id="update.ExclusiveLock" parameterMap="parameter.ExclusiveLockUpdateMap">
|
||||
update
|
||||
alf_lock
|
||||
set
|
||||
version = version + 1,
|
||||
lock_token = lower(?),
|
||||
expiry_time = ?
|
||||
where
|
||||
excl_resource_id = ? and
|
||||
lock_token = ?
|
||||
</update>
|
||||
|
||||
</sqlMap>
|
@@ -1826,4 +1826,16 @@
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="patch.db-V3.2-LockTables" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
|
||||
<property name="id"><value>patch.db-V3.2-LockTables</value></property>
|
||||
<property name="description"><value>patch.schemaUpgradeScript.description</value></property>
|
||||
<property name="fixesFromSchema"><value>0</value></property>
|
||||
<property name="fixesToSchema"><value>2010</value></property>
|
||||
<property name="targetSchema"><value>2011</value></property>
|
||||
<property name="scriptUrl">
|
||||
<!-- Share a create script -->
|
||||
<value>classpath:alfresco/dbscripts/create/3.2/${db.script.dialect}/AlfrescoPostCreate-3.2-LockTables.sql</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
@@ -19,4 +19,4 @@ version.build=@build-number@
|
||||
|
||||
# Schema number
|
||||
|
||||
version.schema=2010
|
||||
version.schema=2011
|
||||
|
@@ -169,7 +169,9 @@ public class HibernateQNameDAOImpl extends HibernateDaoSupport implements QNameD
|
||||
NamespaceEntity namespace = new NamespaceEntityImpl();
|
||||
namespace.setUri(namespaceUri);
|
||||
// Persist
|
||||
Long id = (Long) getSession().save(namespace);
|
||||
Session session = getSession();
|
||||
Long id = (Long) session.save(namespace);
|
||||
DirtySessionMethodInterceptor.flushSession(session, true);
|
||||
// Cache it
|
||||
namespaceEntityCache.put(id, namespaceUri);
|
||||
namespaceEntityCache.put(namespaceUri, id);
|
||||
|
@@ -43,6 +43,8 @@ import org.springframework.dao.ConcurrencyFailureException;
|
||||
*/
|
||||
public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
{
|
||||
private static final String LOCK_TOKEN_RELEASED = "not-locked";
|
||||
|
||||
private QNameDAO qnameDAO;
|
||||
|
||||
/**
|
||||
@@ -61,7 +63,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
this.qnameDAO = qnameDAO;
|
||||
}
|
||||
|
||||
public boolean getLock(QName lockQName, String lockApplicant, long timeToLive)
|
||||
public boolean getLock(QName lockQName, String lockToken, long timeToLive)
|
||||
{
|
||||
String qnameNamespaceUri = lockQName.getNamespaceURI();
|
||||
String qnameLocalName = lockQName.getLocalName();
|
||||
@@ -71,8 +73,8 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
lockQName = QName.createQName(qnameNamespaceUri, qnameLocalName.toLowerCase());
|
||||
qnameLocalName = lockQName.getLocalName();
|
||||
}
|
||||
// Force the lock applicant to lowercase
|
||||
lockApplicant = lockApplicant.toLowerCase();
|
||||
// Force the lock token to lowercase
|
||||
lockToken = lockToken.toLowerCase();
|
||||
|
||||
// Resolve the namespace
|
||||
Long qnameNamespaceId = qnameDAO.getOrCreateNamespace(qnameNamespaceUri).getFirst();
|
||||
@@ -94,7 +96,6 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
{
|
||||
String localname = lockQNameIter.getLocalName();
|
||||
// Get the basic lock resource, forcing a create
|
||||
// TODO: Pull back all lock resources in a single query
|
||||
LockResourceEntity lockResource = getLockResource(qnameNamespaceId, localname);
|
||||
if (lockResource == null)
|
||||
{
|
||||
@@ -105,12 +106,12 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
}
|
||||
|
||||
// Now, get all locks for the resources we will need
|
||||
List<LockEntity> existingLocks = getLocks(requiredLockResourceIds);
|
||||
List<LockEntity> existingLocks = getLocksBySharedResourceIds(requiredLockResourceIds);
|
||||
Map<LockEntity, LockEntity> existingLocksMap = new HashMap<LockEntity, LockEntity>();
|
||||
// Check them and make sure they don't prevent locks
|
||||
for (LockEntity existingLock : existingLocks)
|
||||
{
|
||||
boolean canTakeLock = canTakeLock(existingLock, lockApplicant, requiredExclusiveLockResourceId);
|
||||
boolean canTakeLock = canTakeLock(existingLock, lockToken, requiredExclusiveLockResourceId);
|
||||
if (!canTakeLock)
|
||||
{
|
||||
return false;
|
||||
@@ -129,7 +130,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
{
|
||||
requiredLock = existingLocksMap.get(requiredLock);
|
||||
// Do an update
|
||||
throw new UnsupportedOperationException();
|
||||
updateLock(requiredLock, lockToken, timeToLive);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -137,18 +138,78 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
requiredLock = createLock(
|
||||
requiredLockResourceId,
|
||||
requiredExclusiveLockResourceId,
|
||||
lockApplicant,
|
||||
lockToken,
|
||||
timeToLive);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean canTakeLock(LockEntity existingLock, String lockApplicant, Long desiredExclusiveLock)
|
||||
public boolean refreshLock(QName lockQName, String lockToken, long timeToLive)
|
||||
{
|
||||
if (EqualsHelper.nullSafeEquals(existingLock.getLockHolder(), lockApplicant))
|
||||
return updateLocks(lockQName, lockToken, lockToken, timeToLive);
|
||||
}
|
||||
|
||||
public boolean releaseLock(QName lockQName, String lockToken)
|
||||
{
|
||||
return updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put new values against the given exclusive lock. This works against the related locks as
|
||||
* well.
|
||||
*/
|
||||
private boolean updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive)
|
||||
{
|
||||
String qnameNamespaceUri = lockQName.getNamespaceURI();
|
||||
String qnameLocalName = lockQName.getLocalName();
|
||||
// Force lower case for case insensitivity
|
||||
if (!qnameLocalName.toLowerCase().equals(qnameLocalName))
|
||||
{
|
||||
// The lock applicant to be is also the current lock holder.
|
||||
lockQName = QName.createQName(qnameNamespaceUri, qnameLocalName.toLowerCase());
|
||||
qnameLocalName = lockQName.getLocalName();
|
||||
}
|
||||
// Force the lock token to lowercase
|
||||
lockToken = lockToken.toLowerCase();
|
||||
|
||||
// Resolve the namespace
|
||||
Long qnameNamespaceId = qnameDAO.getOrCreateNamespace(qnameNamespaceUri).getFirst();
|
||||
|
||||
// Get the lock resource for the exclusive lock.
|
||||
// All the locks that are created will need the exclusive case.
|
||||
LockResourceEntity exclusiveLockResource = getLockResource(qnameNamespaceId, qnameLocalName);
|
||||
if (exclusiveLockResource == null)
|
||||
{
|
||||
// If the exclusive lock doesn't exist, the locks don't exist
|
||||
return false;
|
||||
}
|
||||
Long exclusiveLockResourceId = exclusiveLockResource.getId();
|
||||
// Split the lock name
|
||||
List<QName> lockQNames = splitLockQName(lockQName);
|
||||
// We just need to know how many resources needed updating.
|
||||
// They will all share the same exclusive lock resource
|
||||
int requiredUpdateCount = lockQNames.size();
|
||||
// Update
|
||||
int updateCount = updateLocks(exclusiveLockResourceId, lockToken, newLockToken, timeToLive);
|
||||
// Check
|
||||
if (updateCount != requiredUpdateCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a lock can be taken or not.
|
||||
*/
|
||||
private boolean canTakeLock(LockEntity existingLock, String lockToken, Long desiredExclusiveLock)
|
||||
{
|
||||
if (EqualsHelper.nullSafeEquals(existingLock.getLockToken(), lockToken))
|
||||
{
|
||||
// The lock token is the same.
|
||||
// Regardless of lock expiry, the lock can be taken
|
||||
return true;
|
||||
}
|
||||
@@ -159,7 +220,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
}
|
||||
else if (existingLock.isExclusive())
|
||||
{
|
||||
// It's a valid, exclusive lock held by someone else ...
|
||||
// It's a valid, exclusive lock held using a different token ...
|
||||
return false;
|
||||
}
|
||||
else if (desiredExclusiveLock.equals(existingLock.getSharedResourceId()))
|
||||
@@ -177,44 +238,82 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
/**
|
||||
* Override to get the unique, lock resource entity if one exists.
|
||||
*
|
||||
* @param qnameNamespaceId the namespace entity ID
|
||||
* @param qnameLocalName the lock localname
|
||||
* @return Returns the lock resource entity,
|
||||
* or <tt>null</tt> if it doesn't exist
|
||||
* @param qnameNamespaceId the namespace entity ID
|
||||
* @param qnameLocalName the lock localname
|
||||
* @return Returns the lock resource entity,
|
||||
* or <tt>null</tt> if it doesn't exist
|
||||
*/
|
||||
protected abstract LockResourceEntity getLockResource(Long qnameNamespaceId, String qnameLocalName);
|
||||
|
||||
/**
|
||||
* Create a unique lock resource
|
||||
*
|
||||
* @param qnameNamespaceId the namespace entity ID
|
||||
* @param qnameLocalName the lock localname
|
||||
* @return Returns the newly created lock resource entity
|
||||
* @param qnameNamespaceId the namespace entity ID
|
||||
* @param qnameLocalName the lock localname
|
||||
* @return Returns the newly created lock resource entity
|
||||
*/
|
||||
protected abstract LockResourceEntity createLockResource(Long qnameNamespaceId, String qnameLocalName);
|
||||
|
||||
/**
|
||||
* Get any existing lock data for the resources required. The locks returned are not filtered and
|
||||
* @param id the lock instance ID
|
||||
* @return Returns the lock, if it exists, otherwise <tt>null</tt>
|
||||
*/
|
||||
protected abstract LockEntity getLock(Long id);
|
||||
|
||||
/**
|
||||
* @param sharedResourceId the shared lock resource ID
|
||||
* @param exclusiveResourceId the exclusive lock resource ID
|
||||
* @return Returns the lock, if it exists, otherwise <tt>null</tt>
|
||||
*/
|
||||
protected abstract LockEntity getLock(Long sharedResourceId, Long exclusiveResourceId);
|
||||
|
||||
/**
|
||||
* Get any existing lock data for the shared resources. The locks returned are not filtered and
|
||||
* may be expired.
|
||||
*
|
||||
* @param lockResourceIds a list of resource IDs for which to retrieve the current locks
|
||||
* @param lockResourceIds a list of shared resource IDs for which to retrieve the current locks
|
||||
* @return Returns a list of locks (expired or not) for the given lock resources
|
||||
*/
|
||||
protected abstract List<LockEntity> getLocks(List<Long> lockResourceIds);
|
||||
protected abstract List<LockEntity> getLocksBySharedResourceIds(List<Long> sharedLockResourceIds);
|
||||
|
||||
/**
|
||||
* Create a new lock.
|
||||
* @param sharedResourceId the specific resource to lock
|
||||
* @param exclusiveResourceId the exclusive lock that is being sought
|
||||
* @param lockApplicant the ID of the lock applicant
|
||||
* @param timeToLive the time, in milliseconds, for the lock to remain valid
|
||||
* @return Returns the new lock
|
||||
* @param sharedResourceId the specific resource to lock
|
||||
* @param exclusiveResourceId the exclusive lock that is being sought
|
||||
* @param lockToken the lock token to assign
|
||||
* @param timeToLive the time, in milliseconds, for the lock to remain valid
|
||||
* @return Returns the new lock
|
||||
* @throws ConcurrencyFailureException if the lock was already taken at the time of creation
|
||||
*/
|
||||
protected abstract LockEntity createLock(
|
||||
Long sharedResourceId,
|
||||
Long exclusiveResourceId,
|
||||
String lockApplicant,
|
||||
String lockToken,
|
||||
long timeToLive);
|
||||
|
||||
/**
|
||||
* Update an existing lock
|
||||
* @param lockEntity the specific lock to update
|
||||
* @param lockApplicant the new lock token
|
||||
* @param timeToLive the new lock time, in milliseconds, for the lock to remain valid
|
||||
* @return Returns the updated lock
|
||||
*/
|
||||
protected abstract LockEntity updateLock(
|
||||
LockEntity lockEntity,
|
||||
String lockToken,
|
||||
long timeToLive);
|
||||
|
||||
/**
|
||||
* @param exclusiveLockResourceId the exclusive resource ID being locks
|
||||
* @param oldLockToken the lock token to change from
|
||||
* @param newLockToken the new lock token
|
||||
* @param timeToLive the new time to live (in milliseconds)
|
||||
* @return the number of rows updated
|
||||
*/
|
||||
protected abstract int updateLocks(
|
||||
Long exclusiveLockResourceId,
|
||||
String oldLockToken,
|
||||
String newLockToken,
|
||||
long timeToLive);
|
||||
|
||||
/**
|
||||
@@ -222,8 +321,8 @@ public abstract class AbstractLockDAOImpl implements LockDAO
|
||||
* separator on the localname. The namespace is preserved. The provided qualified
|
||||
* name will always be the last component in the returned list.
|
||||
*
|
||||
* @param lockQName the lock name to split into it's higher-level paths
|
||||
* @return Returns the namespace ID along with the ordered localnames
|
||||
* @param lockQName the lock name to split into it's higher-level paths
|
||||
* @return Returns the namespace ID along with the ordered localnames
|
||||
*/
|
||||
protected List<QName> splitLockQName(QName lockQName)
|
||||
{
|
||||
|
@@ -37,12 +37,43 @@ public interface LockDAO
|
||||
/**
|
||||
* Aquire a given exclusive lock, assigning it (and any implicitly shared locks) a
|
||||
* timeout. All shared locks are implicitly taken as well.
|
||||
* <p>
|
||||
* A lock can be re-taken if it has expired and if the lock token has not changed
|
||||
*
|
||||
* @param lockQName the unique name of the lock to acquire
|
||||
* @param lockApplicant the potential lock holder's identifier (max 36 chars)
|
||||
* @param lockToken the potential lock token (max 36 chars)
|
||||
* @param timeToLive the time (in milliseconds) that the lock must remain
|
||||
* @return Returns <tt>true</tt> if the lock was taken,
|
||||
* otherwise <tt>false</tt>
|
||||
*/
|
||||
boolean getLock(QName lockQName, String lockApplicant, long timeToLive);
|
||||
boolean getLock(QName lockQName, String lockToken, long timeToLive);
|
||||
|
||||
/**
|
||||
* Refresh a held lock. This is successful if the lock in question still exists
|
||||
* and if the lock token has not changed. Lock expiry does not prevent the lock
|
||||
* from being refreshed.
|
||||
*
|
||||
* @param lockQName the unique name of the lock to update
|
||||
* @param lockToken the lock token for the lock held
|
||||
* @param timeToLive the new time to live (in milliseconds)
|
||||
* @return Returns <tt>true</tt> if the lock was updated,
|
||||
* otherwise <tt>false</tt>
|
||||
*/
|
||||
boolean refreshLock(QName lockQName, String lockToken, long timeToLive);
|
||||
|
||||
/**
|
||||
* Release a lock. The lock token must still apply and all the shared and exclusive
|
||||
* locks need to still be present. Lock expiration does not prevent this operation
|
||||
* from succeeding.
|
||||
* <p>
|
||||
* Note: Failure to release a lock due to a exception condition is dealt with by
|
||||
* passing the exception out.
|
||||
*
|
||||
* @param lockQName the unique name of the lock to release
|
||||
* @param lockToken the current lock token
|
||||
* @return Returns <tt>true</tt> if all the required locks were
|
||||
* (still) held under the lock token and were
|
||||
* valid at the time of release, otherwise <tt>false</tt>
|
||||
*/
|
||||
boolean releaseLock(QName lockQName, String lockToken);
|
||||
}
|
||||
|
420
source/java/org/alfresco/repo/domain/locks/LockDAOTest.java
Normal file
420
source/java/org/alfresco/repo/domain/locks/LockDAOTest.java
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 Alfresco Software Limited.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program 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 General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
* As a special exception to the terms and conditions of version 2.0 of
|
||||
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||
* FLOSS exception. You should have recieved a copy of the text describing
|
||||
* the FLOSS exception, and it is also available here:
|
||||
* http://www.alfresco.com/legal/licensing"
|
||||
*/
|
||||
package org.alfresco.repo.domain.locks;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @see LockDAO
|
||||
*
|
||||
* @author Derek Hulley
|
||||
* @since 3.2
|
||||
*/
|
||||
public class LockDAOTest extends TestCase
|
||||
{
|
||||
public static final String NAMESPACE = "http://www.alfresco.org/test/LockDAOTest";
|
||||
|
||||
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private TransactionService transactionService;
|
||||
private RetryingTransactionHelper txnHelper;
|
||||
private LockDAO lockDAO;
|
||||
// Lock names for the tests
|
||||
private QName lockA;
|
||||
private QName lockAA;
|
||||
private QName lockAAA;
|
||||
private QName lockAAB;
|
||||
private QName lockAAC;
|
||||
private QName lockAB;
|
||||
private QName lockABA;
|
||||
private QName lockABB;
|
||||
private QName lockABC;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
|
||||
transactionService = serviceRegistry.getTransactionService();
|
||||
txnHelper = transactionService.getRetryingTransactionHelper();
|
||||
txnHelper.setMinRetryWaitMs(10);
|
||||
txnHelper.setRetryWaitIncrementMs(10);
|
||||
txnHelper.setMaxRetryWaitMs(50);
|
||||
|
||||
lockDAO = (LockDAO) ctx.getBean("lockDAO");
|
||||
// Get the test name
|
||||
String testName = getName();
|
||||
// Build lock names for the test
|
||||
lockA = QName.createQName(NAMESPACE, "a-" + testName);
|
||||
lockAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName);
|
||||
lockAAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".a-" + testName);
|
||||
lockAAB = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".b-" + testName);
|
||||
lockAAC = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".c-" + testName);
|
||||
lockAB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName);
|
||||
lockABA = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".a-" + testName);
|
||||
lockABB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".b-" + testName);
|
||||
lockABC = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".c-" + testName);
|
||||
}
|
||||
|
||||
private String lock(final QName lockName, final long timeToLive, boolean expectSuccess)
|
||||
{
|
||||
String token = lock(lockName, timeToLive);
|
||||
if (expectSuccess)
|
||||
{
|
||||
assertNotNull(
|
||||
"Expected to get lock " + lockName + " with TTL of " + timeToLive,
|
||||
token);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertNull(
|
||||
"Expected lock " + lockName + " to have been denied",
|
||||
token);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* Do the lock in a new transaction
|
||||
* @return Returns the lock token or <tt>null</tt> if it didn't work
|
||||
*/
|
||||
private String lock(final QName lockName, final long timeToLive)
|
||||
{
|
||||
RetryingTransactionCallback<String> callback = new RetryingTransactionCallback<String>()
|
||||
{
|
||||
public String execute() throws Throwable
|
||||
{
|
||||
String txnId = AlfrescoTransactionSupport.getTransactionId();
|
||||
boolean locked = lockDAO.getLock(lockName, txnId, timeToLive);
|
||||
return locked ? txnId : null;
|
||||
}
|
||||
};
|
||||
return txnHelper.doInTransaction(callback);
|
||||
}
|
||||
|
||||
private void refresh(final QName lockName, final String lockToken, final long timeToLive, boolean expectSuccess)
|
||||
{
|
||||
RetryingTransactionCallback<Boolean> callback = new RetryingTransactionCallback<Boolean>()
|
||||
{
|
||||
public Boolean execute() throws Throwable
|
||||
{
|
||||
return lockDAO.refreshLock(lockName, lockToken, timeToLive);
|
||||
}
|
||||
};
|
||||
Boolean released = txnHelper.doInTransaction(callback);
|
||||
if (expectSuccess)
|
||||
{
|
||||
assertTrue(
|
||||
"Expected to have refreshed lock " + lockName,
|
||||
released.booleanValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertFalse(
|
||||
"Expected to have failed to refresh lock " + lockName,
|
||||
released.booleanValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void release(final QName lockName, final String lockToken, boolean expectSuccess)
|
||||
{
|
||||
RetryingTransactionCallback<Boolean> callback = new RetryingTransactionCallback<Boolean>()
|
||||
{
|
||||
public Boolean execute() throws Throwable
|
||||
{
|
||||
return lockDAO.releaseLock(lockName, lockToken);
|
||||
}
|
||||
};
|
||||
Boolean released = txnHelper.doInTransaction(callback);
|
||||
if (expectSuccess)
|
||||
{
|
||||
assertTrue(
|
||||
"Expected to have released lock " + lockName,
|
||||
released.booleanValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertFalse(
|
||||
"Expected to have failed to release lock " + lockName,
|
||||
released.booleanValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetLockBasic() throws Exception
|
||||
{
|
||||
lock(lockAAA, 500L, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the lock tables and queries scale
|
||||
*/
|
||||
public void testLockTableScaling() throws Exception
|
||||
{
|
||||
int count = 500;
|
||||
long before = System.currentTimeMillis();
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
QName lockName = QName.createQName(lockAAA.getNamespaceURI(), lockAAA.getLocalName() + "-" + i);
|
||||
lock(lockName, 500L, true);
|
||||
if (i % 100 == 0)
|
||||
{
|
||||
long after = System.currentTimeMillis();
|
||||
System.out.println("Creation of " + i + " locks took " + (after-before)/1000 + "s");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetLockFailureBasic() throws Exception
|
||||
{
|
||||
lock(lockAAA, 500L, true);
|
||||
lock(lockAAA, 0L, false);
|
||||
}
|
||||
|
||||
public void testSharedLocks() throws Exception
|
||||
{
|
||||
lock(lockAAA, 500L, true);
|
||||
lock(lockAAB, 500L, true);
|
||||
lock(lockAAC, 500L, true);
|
||||
lock(lockABA, 500L, true);
|
||||
lock(lockABB, 500L, true);
|
||||
lock(lockABC, 500L, true);
|
||||
}
|
||||
|
||||
public void testExclusiveLockBlockedByShared() throws Exception
|
||||
{
|
||||
lock(lockAAA, 100L, true);
|
||||
lock(lockAA, 100L, false);
|
||||
lock(lockAB, 100L, true);
|
||||
lock(lockA, 100L, false);
|
||||
lock(lockABA, 100L, false);
|
||||
}
|
||||
|
||||
public void testReleaseLockBasic() throws Exception
|
||||
{
|
||||
String token = lock(lockAAA, 500000L, true);
|
||||
release(lockAAA, token, true);
|
||||
token = lock(lockAAA, 0L, true);
|
||||
}
|
||||
|
||||
public void testSharedLockAndRelease() throws Exception
|
||||
{
|
||||
String tokenAAA = lock(lockAAA, 5000L, true);
|
||||
String tokenAAB = lock(lockAAB, 5000L, true);
|
||||
String tokenAAC = lock(lockAAC, 5000L, true);
|
||||
String tokenABA = lock(lockABA, 5000L, true);
|
||||
String tokenABB = lock(lockABB, 5000L, true);
|
||||
String tokenABC = lock(lockABC, 5000L, true);
|
||||
// Can't lock shared resources
|
||||
lock(lockAA, 0L, false);
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockAAA, tokenAAA, true);
|
||||
lock(lockAA, 0L, false);
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockAAB, tokenAAB, true);
|
||||
lock(lockAA, 0L, false);
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockAAC, tokenAAC, true);
|
||||
String tokenAA = lock(lockAA, 5000L, true); // This should be open now
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockABA, tokenABA, true);
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockABB, tokenABB, true);
|
||||
lock(lockAB, 0L, false);
|
||||
lock(lockA, 0L, false);
|
||||
// Release a lock and check again
|
||||
release(lockABC, tokenABC, true);
|
||||
String tokenAB = lock(lockAB, 5000L, true);
|
||||
lock(lockA, 0L, false);
|
||||
// Release AA and AB
|
||||
release(lockAA, tokenAA, true);
|
||||
release(lockAB, tokenAB, true);
|
||||
String tokenA = lock(lockA, 5000L, true);
|
||||
// ... and release
|
||||
release(lockA, tokenA, true);
|
||||
}
|
||||
|
||||
public synchronized void testLockExpiry() throws Exception
|
||||
{
|
||||
lock(lockAAA, 50L, true);
|
||||
this.wait(50L);
|
||||
lock(lockAA, 50L, true);
|
||||
this.wait(50L);
|
||||
lock(lockA, 100L, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that locks grabbed away due to expiry cannot be released
|
||||
* @throws Exception
|
||||
*/
|
||||
public synchronized void testLockExpiryAndRelease() throws Exception
|
||||
{
|
||||
String tokenAAA = lock(lockAAA, 500L, true);
|
||||
release(lockAAA, tokenAAA, true);
|
||||
tokenAAA = lock(lockAAA, 50L, true); // Make sure we can re-acquire the lock
|
||||
this.wait(50L); // Wait for expiry
|
||||
String grabbedTokenAAAA = lock(lockAAA, 50L, true); // Grabbed lock over the expiry
|
||||
release(lockAAA, tokenAAA, false); // Can't release any more
|
||||
this.wait(50L); // Wait for expiry
|
||||
release(lockAAA, grabbedTokenAAAA, true); // Proof that expiry, on it's own, doesn't prevent release
|
||||
}
|
||||
|
||||
public synchronized void testLockRefresh() throws Exception
|
||||
{
|
||||
String tokenAAA = lock(lockAAA, 1000L, true);
|
||||
// Loop, refreshing and testing
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
wait(50L);
|
||||
// It will have expired, but refresh it anyway
|
||||
refresh(lockAAA, tokenAAA, 1000L, true);
|
||||
// Check that it is still holding
|
||||
lock(lockAAA, 0L, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a thread lock to ensure that the lock DAO only allows locks through one at a time.
|
||||
*/
|
||||
public synchronized void testConcurrentLockAquisition() throws Exception
|
||||
{
|
||||
ReentrantLock threadLock = new ReentrantLock();
|
||||
GetLockThread[] threads = new GetLockThread[50];
|
||||
for (int i = 0; i < threads.length; i++)
|
||||
{
|
||||
threads[i] = new GetLockThread(threadLock);
|
||||
threads[i].start();
|
||||
}
|
||||
// Wait a bit and see if any encountered errors
|
||||
boolean allDone = false;
|
||||
waitLoop:
|
||||
for (int waitLoop = 0; waitLoop < 50; waitLoop++)
|
||||
{
|
||||
wait(2000L);
|
||||
for (int i = 0; i < threads.length; i++)
|
||||
{
|
||||
if (!threads[i].done)
|
||||
{
|
||||
continue waitLoop;
|
||||
}
|
||||
}
|
||||
// All the threads are done
|
||||
allDone = true;
|
||||
break;
|
||||
}
|
||||
// Check that all the threads got a turn
|
||||
if (!allDone)
|
||||
{
|
||||
fail("Not all threads managed to acquire the lock");
|
||||
}
|
||||
// Get errors
|
||||
StringBuilder errors = new StringBuilder(512);
|
||||
for (int i = 0; i < threads.length; i++)
|
||||
{
|
||||
if (threads[i].error != null)
|
||||
{
|
||||
errors.append("\nThread ").append(i).append(" error: ").append(threads[i].error);
|
||||
}
|
||||
}
|
||||
if (errors.toString().length() > 0)
|
||||
{
|
||||
fail(errors.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the lock via the DAO forces a serialization
|
||||
*/
|
||||
private class GetLockThread extends Thread
|
||||
{
|
||||
private final ReentrantLock threadLock;
|
||||
private boolean done;
|
||||
private String error;
|
||||
private GetLockThread(ReentrantLock threadLock)
|
||||
{
|
||||
this.threadLock = threadLock;
|
||||
this.done = false;
|
||||
this.error = null;
|
||||
setDaemon(true);
|
||||
}
|
||||
@Override
|
||||
public synchronized void run()
|
||||
{
|
||||
boolean gotLock = false;
|
||||
try
|
||||
{
|
||||
String tokenAAA = null;
|
||||
while (true)
|
||||
{
|
||||
tokenAAA = lock(lockAAA, 100000L); // Lock for a long time
|
||||
if (tokenAAA != null)
|
||||
{
|
||||
break; // Got the lock
|
||||
}
|
||||
try { wait(20L); } catch (InterruptedException e) {}
|
||||
}
|
||||
gotLock = threadLock.tryLock(0, TimeUnit.MILLISECONDS);
|
||||
if (!gotLock)
|
||||
{
|
||||
error = "Got lock via DAO but not via thread lock";
|
||||
return;
|
||||
}
|
||||
release(lockAAA, tokenAAA, true);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
error = e.getMessage();
|
||||
}
|
||||
finally
|
||||
{
|
||||
done = true;
|
||||
if (gotLock)
|
||||
{
|
||||
threadLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -41,9 +41,9 @@ public class LockEntity
|
||||
private Long version;
|
||||
private Long sharedResourceId;
|
||||
private Long exclusiveResourceId;
|
||||
private String lockHolder;
|
||||
private String lockToken;
|
||||
private Long startTime;
|
||||
private Long expiryTime = Long.MAX_VALUE; // TODO:
|
||||
private Long expiryTime = Long.MIN_VALUE; // 'expired' unless set
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
@@ -144,19 +144,19 @@ public class LockEntity
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the ID of the lock holder
|
||||
* @return Returns the token assigned when the lock was created
|
||||
*/
|
||||
public String getLockHolder()
|
||||
public String getLockToken()
|
||||
{
|
||||
return lockHolder;
|
||||
return lockToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lockHolder the ID of the lock holder
|
||||
* @param lockToken the token assigned when the lock was created
|
||||
*/
|
||||
public void setLockHolder(String lockHolder)
|
||||
public void setLockToken(String lockToken)
|
||||
{
|
||||
this.lockHolder = lockHolder;
|
||||
this.lockToken = lockToken;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -24,7 +24,9 @@
|
||||
*/
|
||||
package org.alfresco.repo.domain.locks.ibatis;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.domain.locks.AbstractLockDAOImpl;
|
||||
import org.alfresco.repo.domain.locks.LockEntity;
|
||||
@@ -41,9 +43,13 @@ public class LockDAOImpl extends AbstractLockDAOImpl
|
||||
{
|
||||
private static final Long CONST_LONG_ZERO = new Long(0L);
|
||||
private static final String SELECT_LOCKRESOURCE_BY_QNAME = "select.LockResourceByQName";
|
||||
private static final String SELECT_LOCK_BY_ID = "select.LockByID";
|
||||
private static final String SELECT_LOCK_BY_KEY = "select.LockByKey";
|
||||
private static final String SELECT_LOCK_BY_SHARED_IDS = "select.LockBySharedIds";
|
||||
private static final String INSERT_LOCKRESOURCE = "insert.LockResource";
|
||||
private static final String INSERT_LOCK = "insert.Lock";
|
||||
private static final String UPDATE_LOCK = "update.Lock";
|
||||
private static final String UPDATE_EXCLUSIVE_LOCK = "update.ExclusiveLock";
|
||||
|
||||
private SqlMapClientTemplate template;
|
||||
|
||||
@@ -78,25 +84,46 @@ public class LockDAOImpl extends AbstractLockDAOImpl
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected List<LockEntity> getLocks(List<Long> lockResourceIds)
|
||||
protected List<LockEntity> getLocksBySharedResourceIds(List<Long> sharedLockResourceIds)
|
||||
{
|
||||
List<LockEntity> locks = template.queryForList(SELECT_LOCK_BY_SHARED_IDS, lockResourceIds);
|
||||
List<LockEntity> locks = template.queryForList(SELECT_LOCK_BY_SHARED_IDS, sharedLockResourceIds);
|
||||
// Done
|
||||
return locks;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LockEntity getLock(Long id)
|
||||
{
|
||||
LockEntity lock = new LockEntity();
|
||||
lock.setId(id);
|
||||
lock = (LockEntity) template.queryForObject(SELECT_LOCK_BY_ID, lock);
|
||||
// Done
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LockEntity getLock(Long sharedResourceId, Long exclusiveResourceId)
|
||||
{
|
||||
LockEntity lock = new LockEntity();
|
||||
lock.setSharedResourceId(sharedResourceId);
|
||||
lock.setExclusiveResourceId(exclusiveResourceId);
|
||||
lock = (LockEntity) template.queryForObject(SELECT_LOCK_BY_KEY, lock);
|
||||
// Done
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LockEntity createLock(
|
||||
Long sharedResourceId,
|
||||
Long exclusiveResourceId,
|
||||
String lockApplicant,
|
||||
String lockToken,
|
||||
long timeToLive)
|
||||
{
|
||||
LockEntity lock = new LockEntity();
|
||||
lock.setVersion(CONST_LONG_ZERO);
|
||||
lock.setSharedResourceId(sharedResourceId);
|
||||
lock.setExclusiveResourceId(exclusiveResourceId);
|
||||
lock.setLockHolder(lockApplicant);
|
||||
lock.setLockToken(lockToken);
|
||||
long now = System.currentTimeMillis();
|
||||
long exp = now + timeToLive;
|
||||
lock.setStartTime(now);
|
||||
@@ -106,4 +133,42 @@ public class LockDAOImpl extends AbstractLockDAOImpl
|
||||
// Done
|
||||
return lock;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LockEntity updateLock(LockEntity lockEntity, String lockToken, long timeToLive)
|
||||
{
|
||||
LockEntity updateLockEntity = new LockEntity();
|
||||
updateLockEntity.setId(lockEntity.getId());
|
||||
updateLockEntity.setVersion(lockEntity.getVersion());
|
||||
updateLockEntity.incrementVersion(); // Increment the version number
|
||||
updateLockEntity.setSharedResourceId(lockEntity.getSharedResourceId());
|
||||
updateLockEntity.setExclusiveResourceId(lockEntity.getExclusiveResourceId());
|
||||
updateLockEntity.setLockToken(lockToken);
|
||||
long now = System.currentTimeMillis();
|
||||
Long exp = now + timeToLive;
|
||||
updateLockEntity.setStartTime(lockEntity.getStartTime()); // Keep original start time
|
||||
updateLockEntity.setExpiryTime(exp); // Don't update the start time
|
||||
template.update(UPDATE_LOCK, updateLockEntity, 1);
|
||||
// Done
|
||||
return updateLockEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int updateLocks(
|
||||
Long exclusiveLockResourceId,
|
||||
String oldLockToken,
|
||||
String newLockToken,
|
||||
long timeToLive)
|
||||
{
|
||||
Map<String, Object> params = new HashMap<String, Object>(11);
|
||||
params.put("exclusiveLockResourceId", exclusiveLockResourceId);
|
||||
params.put("oldLockToken", oldLockToken);
|
||||
params.put("newLockToken", oldLockToken);
|
||||
long now = System.currentTimeMillis();
|
||||
Long exp = new Long(now + timeToLive);
|
||||
params.put("newExpiryTime", exp);
|
||||
int updateCount = template.update(UPDATE_EXCLUSIVE_LOCK, params);
|
||||
// Done
|
||||
return updateCount;
|
||||
}
|
||||
}
|
||||
|
@@ -54,6 +54,7 @@ import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.dao.ConcurrencyFailureException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.dao.DeadlockLoserDataAccessException;
|
||||
import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException;
|
||||
import org.springframework.jdbc.UncategorizedSQLException;
|
||||
|
||||
import com.ibatis.common.jdbc.exception.NestedSQLException;
|
||||
@@ -95,6 +96,7 @@ public class RetryingTransactionHelper
|
||||
ConcurrencyFailureException.class,
|
||||
DeadlockLoserDataAccessException.class,
|
||||
StaleObjectStateException.class,
|
||||
JdbcUpdateAffectedIncorrectNumberOfRowsException.class, // Similar to StaleObjectState
|
||||
LockAcquisitionException.class,
|
||||
ConstraintViolationException.class,
|
||||
UncategorizedSQLException.class,
|
||||
@@ -137,7 +139,7 @@ public class RetryingTransactionHelper
|
||||
|
||||
/**
|
||||
* Callback interface
|
||||
* @author britt
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface RetryingTransactionCallback<Result>
|
||||
{
|
||||
|
Reference in New Issue
Block a user