/* * Copyright (C) 2014 University of Toronto, Computational Biology Lab. * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package org.ut.biolab.medsavant.server.db; import java.util.HashMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ut.biolab.medsavant.shared.model.exception.LockException; /** * Governs the locking of the database for background tasks which alter the * database. * * @author mfiume */ public class LockController { private class Key { String database; int projectId; Key(String database, int projectId) { this.database = database; this.projectId = projectId; } public String getDatabase() { return database; } public int getProjectId() { return projectId; } @Override public String toString() { return this.database + "_" + projectId; } @Override public boolean equals(Object o) { if (o instanceof Key) { Key k = (Key) o; return database.equals(k.database) && (projectId == k.projectId); } return false; } @Override public int hashCode() { return toString().hashCode(); } } private static Log LOG = LogFactory.getLog(LockController.class); private static LockController instance; private final HashMap<Key, ReentrantLock> locks; private LockController() { locks = new HashMap<Key, ReentrantLock>(); } public static LockController getInstance() { if (instance == null) { instance = new LockController(); } return instance; } /** * Obtain a lock. If another thread already owns the lock, an exception will * be thrown. * * @param database The database with the project to be unlocked. * @param projectID The project ID to lock * @throws LockException */ public synchronized void requestLock(String database, int projectID) throws LockException { LOG.info("Server lock requested"); Key key = new Key(database, projectID); ReentrantLock lock = locks.get(key); // create the lock if one doesn't exist for the project if (lock == null) { lock = new ReentrantLock(); locks.put(key, lock); } // lock it down // (if this thread owns the lock, call lock again which will increase the hold count) if (!lock.isLocked() || lock.isHeldByCurrentThread()) { lock.lock(); LOG.info(String.format("Server locked - hold count %d", lock.getHoldCount())); } else { throw new LockException("Database is locked for changes"); } } /** * Release the lock unforcibly. * * @param database The database with the project to be unlocked. * @param projectID The project ID to lock * @throws org.ut.biolab.medsavant.shared.model.LockException */ public synchronized void releaseLock(String database, int projectID) throws LockException { releaseLock(database, projectID, false); } /** * Release the lock. If force is false, only the thread which requested the * lock may unlock. However, in the case where that thread no longer exists, * it is necessary to force the unlock. * * @param database The database containing the project to unlock. * @param projectID The project ID to unlock. * @param force Whether to force the unlock. * @throws org.ut.biolab.medsavant.shared.model.LockException */ public synchronized void releaseLock(String database, int projectID, boolean force) throws LockException { LOG.info("Server unlock requested"); Key k = new Key(database, projectID); ReentrantLock lock = locks.get(k); // no lock exists for this project if (lock == null) { throw new LockException("No lock exists"); } if (force) { while (lock.isLocked()) { lock.unlock(); } LOG.info(String.format("Server forcibly unlocked - hold count %d", lock.getHoldCount())); // unlock it, or decrement the hold count } else if (lock.isHeldByCurrentThread()) { lock.unlock(); LOG.info(String.format("Server unlocked - hold count %d", lock.getHoldCount())); // not allowed to lock } else { throw new LockException("Database could not be unlocked"); } } /** * Check if the project is locked * * @param database The database containing the project to unlock. * @param projectID The project ID to check. * @return Whether the project is locked. */ public boolean isLocked(String database, int projectID) { ReentrantLock l = locks.get(new Key(database, projectID)); // l will be null if no one has requested a lock for the project before if (l == null) { return false; } return l.isLocked(); } }