/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos 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.
Cyclos 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 Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.utils.lock;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.exceptions.LockingException;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.sms.MemberSmsStatusLock;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.ExceptionHelper;
import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData;
import nl.strohalm.cyclos.utils.transaction.TransactionRollbackListener;
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.JDBCException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
/**
* Returns {@link LockHandler}s which use the current transaction to perform locks. Suitable for most cases, except replicated databases (like MySQL
* master-master replication).
*
* @author luis
*/
public class DirectLockHandlerFactory extends BaseLockHandlerFactory {
/**
* Locks records in the current transaction
* @author luis
*/
private class DirectLockHandler implements LockHandler {
@Override
public void lock(final Account... accounts) throws LockingException {
if (ArrayUtils.isEmpty(accounts)) {
return;
}
Long[] ids = EntityHelper.toIds(accounts);
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
try {
session
.createQuery("select l.id from AccountLock l where l.id in (:ids)")
.setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE))
.setParameterList("ids", ids)
.list();
} catch (JDBCException e) {
handleException(e);
}
}
@Override
public void lockSmsStatus(final Member member) throws LockingException {
if (member == null) {
return;
}
final Session session = SessionFactoryUtils.getSession(sessionFactory, true);
try {
Long id = (Long) session
.createQuery("select m.id from MemberSmsStatusLock m where m.id = :id")
.setLockOptions(new LockOptions(LockMode.PESSIMISTIC_WRITE))
.setParameter("id", member.getId())
.uniqueResult();
// If the id didn't exist, insert it and throw a LockingException, so the next attempt will succeed
if (id == null) {
CurrentTransactionData.addTransactionRollbackListener(new TransactionRollbackListener() {
@Override
public void onTransactionRollback() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(final TransactionStatus status) {
MemberSmsStatusLock lock = new MemberSmsStatusLock();
lock.setId(member.getId());
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.save(lock);
}
});
}
});
throw new LockingException("First time this member sms status. Please, retry.");
}
} catch (JDBCException e) {
handleException(e);
}
}
@Override
public void release() {
// Nothing to do, as when the current transaction ends, all locks will be released
}
private void handleException(final JDBCException e) {
if (ExceptionHelper.isLockingException(e)) {
throw new LockingException(e);
}
throw e;
}
}
private SessionFactory sessionFactory;
private TransactionTemplate transactionTemplate;
private final LockHandler lockHandler = new DirectLockHandler();
@Override
public LockHandler getLockHandler() {
return lockHandler;
}
public void setSessionFactory(final SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void setTransactionTemplate(final TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
}