Java tutorial
/******************************************************************************* * Copyright (c)2014 Prometheus Consulting * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package nz.co.senanque.locking.sql; import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.locks.Lock; import javax.sql.DataSource; import nz.co.senanque.locking.LockFactory; import nz.co.senanque.locking.LockWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; /** * The SQL Lock Factory uses a db table to hold the locks * To get an exclusive it creates an entry for the lock in a table * constrained to be unique, so that will either give us the lock or fail. * To release the lock we just have to remove the record. * * Old locks are cleared when we restart. This means we can use this across multiple * JVMs and if one lock goes bad we can restart just one JVM to clear the bad lock. * * @author Roger Parkinson * */ public class SQLLockFactory implements LockFactory, InitializingBean { static Logger logger = LoggerFactory.getLogger(SQLLockFactory.class); ThreadLocal<List<LockedObject>> m_currentLocks = new ThreadLocal<List<LockedObject>>(); private int m_sleepTime = 1000; private String m_prefix = ""; private String m_hostAddress; private DataSource m_dataSource; private int m_maxRetries = -1; private String getThreadid() { return m_hostAddress + Thread.currentThread().getName() + "-" + Thread.currentThread().getId(); } /* (non-Javadoc) * @see com.aldous.locking.LockFactory#getLock(java.lang.String, com.aldous.locking.LockFactory.LockType) */ public Lock getLock(String lockName, LockType type, String comment) { return new SQLLock(lockName, type, comment, getSleepTime(), this); } public Lock getWrappedLock(String lockName, LockType type, String comment) { Lock lock = getLock(lockName, type, comment); String ownerName = getThreadid(); Connection connection = null; try { connection = m_dataSource.getConnection(); PreparedStatement read = null; ResultSet rs = null; try { read = connection.prepareStatement("select * from SQL_LOCK where lockName = ?"); read.setString(1, lockName); rs = read.executeQuery(); if (rs.next()) { String lockOwner = rs.getString("ownerName"); if (lockOwner.equals(ownerName)) { // We own this lock so we are okay return new LockWrapper(lock, true); } } } catch (SQLException e1) { throw new RuntimeException(e1); } finally { try { if (rs != null) { rs.close(); } if (read != null) { read.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } } catch (SQLException e) { throw new RuntimeException(e); } return new LockWrapper(lock, false); } public boolean lock(SQLLock oracleLock) { String lockName = oracleLock.getLockName(); String ownerName = getThreadid(); Connection connection = null; try { connection = m_dataSource.getConnection(); PreparedStatement read = null; ResultSet rs = null; try { read = connection.prepareStatement("select * from SQL_LOCK where lockName = ?"); read.setString(1, lockName); rs = read.executeQuery(); if (rs.next()) { String lockOwner = rs.getString("ownerName"); if (lockOwner.equals(ownerName)) { // We own this lock so we are okay logger.debug("Lock {} already secured for us: {}", lockName, ownerName); return true; } // we found a lock that we don't own. logger.debug("Lock {} already secured for {} rejected request from {}", new Object[] { lockName, lockOwner, ownerName }); return false; } } catch (SQLException e1) { throw new RuntimeException(e1); } finally { if (rs != null) { rs.close(); } if (read != null) { read.close(); } } // The item is probably not already locked (ie it might get locked between the time we checked and now) // So attempt to lock it by creating a record with that unique key PreparedStatement insert = null; try { insert = connection.prepareStatement( "insert into SQL_LOCK (lockName,ownerName,started,comments,hostAddress) values (?,?,?,?,?)"); insert.setString(1, lockName); insert.setString(2, ownerName); Date d = new Date(); insert.setTimestamp(3, new Timestamp(d.getTime())); insert.setString(4, oracleLock.getComment()); insert.setString(5, m_hostAddress); int count = insert.executeUpdate(); return (count == 1); } catch (SQLException e1) { return false; // failure to insert the record means it must be locked. // throw new RuntimeException(e1); } finally { if (insert != null) { insert.close(); } } } catch (SQLException e) { throw new RuntimeException(e); } finally { close(connection); } } private void close(Connection connection) { try { if (connection != null) { connection.close(); } } catch (SQLException e) { } } public void unlock(SQLLock oracleLock) { unlock(oracleLock.getLockName()); } public void unlock(String plockName) { String lockName = plockName; String ownerName = getThreadid(); Connection connection = null; try { connection = m_dataSource.getConnection(); PreparedStatement read = null; ResultSet rs = null; try { read = connection.prepareStatement("select * from SQL_LOCK where lockName = ?"); read.setString(1, lockName); rs = read.executeQuery(); if (rs.next()) { String lockOwner = rs.getString("ownerName"); if (!lockOwner.equals(ownerName)) { // We don't own this lock logger.debug("Lock {} unreleased for {}", lockName, ownerName); throw new SQLLockException("attempt to release a lock you don't own"); } // we found a lock that we do own so we are okay } else { logger.debug("Lock {} unreleased for {}", lockName, ownerName); throw new SQLLockException("attempt to release a lock you don't own"); } } catch (SQLException e1) { throw new RuntimeException(e1); } finally { if (rs != null) { rs.close(); } if (read != null) { read.close(); } } // The item is probably not already locked (ie it might get locked between the time we checked and now) // So attempt to lock it by creating a record with that unique key PreparedStatement delete = null; try { delete = connection.prepareStatement("delete from SQL_LOCK where lockName = ?"); delete.setString(1, lockName); int count = delete.executeUpdate(); logger.debug("updated lock rows: {}", count); logger.debug("Lock {} released for {}", lockName, ownerName); } catch (SQLException e1) { throw new RuntimeException(e1); } finally { if (delete != null) { delete.close(); } } } catch (SQLException e) { throw new RuntimeException(e); } finally { close(connection); } } public String getPrefix() { return m_prefix; } public void setPrefix(String prefix) { m_prefix = prefix; } public void afterPropertiesSet() throws Exception { try { InetAddress addr = InetAddress.getLocalHost(); // Get IP Address m_hostAddress = getPrefix() + addr.getHostAddress(); } catch (UnknownHostException e) { throw new RuntimeException(e); } } @SuppressWarnings("unused") private void clearOldLocks() { Connection connection = null; try { connection = m_dataSource.getConnection(); PreparedStatement delete = null; try { delete = connection.prepareStatement("delete from SQL_LOCK where hostAddress = ?"); delete.setString(1, m_hostAddress); int count = delete.executeUpdate(); logger.debug("updated lock rows: {}", count); } catch (SQLException e1) { throw new RuntimeException(e1); } finally { if (delete != null) { delete.close(); } } } catch (SQLException e) { throw new RuntimeException(e); } finally { close(connection); } } public int getSleepTime() { return m_sleepTime; } public void setSleepTime(int sleepTime) { m_sleepTime = sleepTime; } public DataSource getDataSource() { return m_dataSource; } public void setDataSource(DataSource dataSource) { m_dataSource = dataSource; } public List<LockedObject> getCurrentLocks() { List<LockedObject> ret = m_currentLocks.get(); if (ret == null) { ret = new ArrayList<LockedObject>(); m_currentLocks.set(ret); } ret = new ArrayList<LockedObject>(); ret.addAll(m_currentLocks.get()); return ret; } public void clearCurrentLocks() { for (LockedObject lock : getCurrentLocks()) { unlock(lock.getObjectName()); } } public long countAllLocks() { return getAllLocks().size(); } public List<String> getAllLocks() { List<String> ret = new ArrayList<String>(); Connection connection = null; try { connection = m_dataSource.getConnection(); PreparedStatement read = null; ResultSet rs = null; try { read = connection.prepareStatement("select * from SQL_LOCK"); rs = read.executeQuery(); while (rs.next()) { StringBuilder sb = new StringBuilder(); sb.append(rs.getString("lockName")); sb.append(" "); sb.append(rs.getString("ownerName")); sb.append(" "); String ts = rs.getString("started"); sb.append(String.valueOf(ts)); sb.append(" "); sb.append(rs.getString("comments")); ret.add(sb.toString()); } } catch (SQLException e1) { throw new RuntimeException(e1); } finally { if (rs != null) { rs.close(); } if (read != null) { read.close(); } } } catch (SQLException e) { throw new RuntimeException(e); } finally { close(connection); } return ret; } public int getMaxRetries() { return m_maxRetries; } public void setMaxRetries(int maxRetries) { m_maxRetries = maxRetries; } }