/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.lock;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.alfresco.repo.domain.locks.LockDAO;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.TraceableThreadFactory;
import org.alfresco.util.VmShutdownListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* {@inheritDoc JobLockService}
*
* @author Derek Hulley
* @since 3.2
*/
public class JobLockServiceImpl implements JobLockService
{
private static final String KEY_RESOURCE_LOCKS = "JobLockServiceImpl.Locks";
private static Log logger = LogFactory.getLog(JobLockServiceImpl.class);
private LockDAO lockDAO;
private RetryingTransactionHelper retryingTransactionHelper;
private int defaultRetryCount;
private long defaultRetryWait;
private ScheduledExecutorService scheduler;
private VmShutdownListener shutdownListener;
/**
* Stateless listener that does post-transaction cleanup.
*/
private final LockTransactionListener txnListener;
public JobLockServiceImpl()
{
defaultRetryWait = 20;
defaultRetryCount = 10;
txnListener = new LockTransactionListener();
TraceableThreadFactory threadFactory = new TraceableThreadFactory();
threadFactory.setThreadDaemon(false);
threadFactory.setNamePrefix("JobLockService");
scheduler = Executors.newSingleThreadScheduledExecutor(threadFactory);
shutdownListener = new VmShutdownListener("JobLockService");
}
/**
* Lifecycle method. This method should be called when the JobLockService
* is no longer required allowing proper clean up before disposing of the object.
*
* This is mostly used to tell the thread pool to shut itself down
* so as to allow the JVM to terminate.
*/
public void shutdown()
{
if (logger.isInfoEnabled())
{
logger.info("shutting down.");
}
// If we don't tell the thread pool to shutdown, then the JVM won't shutdown.
scheduler.shutdown();
}
/**
* Set the lock DAO
*/
public void setLockDAO(LockDAO lockDAO)
{
this.lockDAO = lockDAO;
}
/**
* Set the helper that will handle low-level concurrency conditions i.e. that
* enforces optimistic locking and deals with stale state issues.
*/
public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper)
{
this.retryingTransactionHelper = retryingTransactionHelper;
}
/**
* Set the maximum number of attempts to make at getting a lock
* @param defaultRetryCount the number of attempts
*/
public void setDefaultRetryCount(int defaultRetryCount)
{
this.defaultRetryCount = defaultRetryCount;
}
/**
* Set the default time to wait between attempts to acquire a lock
* @param defaultRetryWait the wait time in milliseconds
*/
public void setDefaultRetryWait(long defaultRetryWait)
{
this.defaultRetryWait = defaultRetryWait;
}
/**
* {@inheritDoc}
*/
@Override
public void getTransactionalLock(QName lockQName, long timeToLive)
{
getTransactionalLock(lockQName, timeToLive, defaultRetryWait, defaultRetryCount);
}
/**
* {@inheritDoc}
*/
@Override
public void getTransactionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount)
{
// Check that transaction is present
final String txnId = AlfrescoTransactionSupport.getTransactionId();
if (txnId == null)
{
throw new IllegalStateException("Locking requires an active transaction");
}
// Get the set of currently-held locks
TreeSet heldLocks = TransactionalResourceHelper.getTreeSet(KEY_RESOURCE_LOCKS);
// We don't want the lock registered as being held if something goes wrong
TreeSet heldLocksTemp = new TreeSet(heldLocks);
boolean added = heldLocksTemp.add(lockQName);
if (!added)
{
// It's a refresh. Ordering is not important here as we already hold the lock.
refreshLock(txnId, lockQName, timeToLive);
}
else
{
QName lastLock = heldLocksTemp.last();
if (lastLock.equals(lockQName))
{
if (logger.isDebugEnabled())
{
logger.debug(
"Attempting to acquire ordered lock: \n" +
" Lock: " + lockQName + "\n" +
" TTL: " + timeToLive + "\n" +
" Txn: " + txnId);
}
// If it was last in the set, then the order is correct and we use the
// full retry behaviour.
getLockImpl(txnId, lockQName, timeToLive, retryWait, retryCount);
}
else
{
if (logger.isDebugEnabled())
{
logger.debug(
"Attempting to acquire UNORDERED lock: \n" +
" Lock: " + lockQName + "\n" +
" TTL: " + timeToLive + "\n" +
" Txn: " + txnId);
}
// The lock request is made out of natural order.
// Unordered locks do not get any retry behaviour
getLockImpl(txnId, lockQName, timeToLive, retryWait, 1);
}
}
// It went in, so add it to the transactionally-stored set
heldLocks.add(lockQName);
// Done
}
/**
* {@inheritDoc}
*
* @see #getLock(QName, long, long, int)
*/
@Override
public String getLock(QName lockQName, long timeToLive)
{
return getLock(lockQName, timeToLive, defaultRetryWait, defaultRetryCount);
}
/**
* {@inheritDoc}
*/
@Override
public String getLock(QName lockQName, long timeToLive, long retryWait, int retryCount)
{
String lockToken = GUID.generate();
getLockImpl(lockToken, lockQName, timeToLive, retryWait, retryCount);
// Done
return lockToken;
}
/**
* {@inheritDoc}
*
* @throws LockAcquisitionException on failure
*/
@Override
public void refreshLock(final String lockToken, final QName lockQName, final long timeToLive)
{
RetryingTransactionCallback