blob: 7fbd7266658aa00f0a3fedd00068ff5930f56163 [file] [log] [blame]
/**
* Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.emf.cdo.server.internal.db;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.lock.CDOLockUtil;
import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea;
import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockArea.Handler;
import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockAreaAlreadyExistsException;
import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockAreaNotFoundException;
import org.eclipse.emf.cdo.common.lock.IDurableLockingManager.LockGrade;
import org.eclipse.emf.cdo.server.db.IDBStoreAccessor;
import org.eclipse.emf.cdo.server.db.IIDHandler;
import org.eclipse.emf.cdo.server.db.IPreparedStatementCache;
import org.eclipse.emf.cdo.server.db.IPreparedStatementCache.ReuseProbability;
import org.eclipse.emf.cdo.spi.common.branch.InternalCDOBranchManager;
import org.eclipse.emf.cdo.spi.server.InternalLockManager;
import org.eclipse.net4j.db.DBException;
import org.eclipse.net4j.db.DBType;
import org.eclipse.net4j.db.DBUtil;
import org.eclipse.net4j.db.ddl.IDBField;
import org.eclipse.net4j.db.ddl.IDBIndex;
import org.eclipse.net4j.db.ddl.IDBSchema;
import org.eclipse.net4j.db.ddl.IDBTable;
import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType;
import org.eclipse.net4j.util.lifecycle.Lifecycle;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* @author Eike Stepper
*/
public class DurableLockingManager extends Lifecycle
{
private DBStore store;
private InternalCDOBranchManager branchManager;
private IIDHandler idHandler;
private IDBTable lockAreas;
private IDBField lockAreasID;
private IDBField lockAreasUser;
private IDBField lockAreasBranch;
private IDBField lockAreasTime;
private IDBField lockAreasReadOnly;
private IDBTable locks;
private IDBField locksArea;
private IDBField locksObject;
private IDBField locksGrade;
private String sqlInsertLockArea;
private String sqlSelectLockArea;
private String sqlSelectAllLockAreas;
private String sqlSelectLockAreas;
private String sqlDeleteLockArea;
private String sqlSelectLocks;
private String sqlSelectLock;
private String sqlInsertLock;
private String sqlUpdateLock;
private String sqlDeleteLock;
private String sqlDeleteLocks;
public DurableLockingManager(DBStore store)
{
this.store = store;
}
public synchronized LockArea createLockArea(DBStoreAccessor accessor, String durableLockingID, String userID,
CDOBranchPoint branchPoint, boolean readOnly, Map<CDOID, LockGrade> locks)
{
try
{
if (durableLockingID == null)
{
durableLockingID = getNextDurableLockingID(accessor);
}
else
{
// If the caller is specifying the ID, make sure there is no area with this ID yet
//
try
{
getLockArea(accessor, durableLockingID);
throw new LockAreaAlreadyExistsException(durableLockingID);
}
catch (LockAreaNotFoundException good)
{
}
}
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlInsertLockArea, ReuseProbability.LOW);
stmt.setString(1, durableLockingID);
stmt.setString(2, userID);
stmt.setInt(3, branchPoint.getBranch().getID());
stmt.setLong(4, branchPoint.getTimeStamp());
stmt.setBoolean(5, readOnly);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
if (!locks.isEmpty())
{
insertLocks(accessor, durableLockingID, locks);
}
accessor.getConnection().commit();
return CDOLockUtil.createLockArea(durableLockingID, userID, branchPoint, readOnly, locks);
}
catch (SQLException ex)
{
throw new DBException(ex);
}
}
private void insertLocks(DBStoreAccessor accessor, String durableLockingID, Map<CDOID, LockGrade> locks)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlInsertLock, ReuseProbability.MEDIUM);
stmt.setString(1, durableLockingID);
for (Entry<CDOID, LockGrade> entry : locks.entrySet())
{
CDOID id = entry.getKey();
int grade = entry.getValue().getValue();
idHandler.setCDOID(stmt, 2, id);
stmt.setInt(3, grade);
DBUtil.update(stmt, true);
}
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
public LockArea getLockArea(DBStoreAccessor accessor, String durableLockingID) throws LockAreaNotFoundException
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
ResultSet resultSet = null;
try
{
stmt = statementCache.getPreparedStatement(sqlSelectLockArea, ReuseProbability.MEDIUM);
stmt.setString(1, durableLockingID);
resultSet = stmt.executeQuery();
if (!resultSet.next())
{
throw new LockAreaNotFoundException(durableLockingID);
}
String userID = resultSet.getString(1);
int branchID = resultSet.getInt(2);
long timeStamp = resultSet.getLong(3);
boolean readOnly = resultSet.getBoolean(4);
return makeLockArea(accessor, durableLockingID, userID, branchID, timeStamp, readOnly);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmt);
}
}
public void getLockAreas(DBStoreAccessor accessor, String userIDPrefix, Handler handler)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
ResultSet resultSet = null;
try
{
if (userIDPrefix.length() == 0)
{
stmt = statementCache.getPreparedStatement(sqlSelectAllLockAreas, ReuseProbability.MEDIUM);
}
else
{
stmt = statementCache.getPreparedStatement(sqlSelectLockAreas, ReuseProbability.MEDIUM);
stmt.setString(1, userIDPrefix + "%");
}
resultSet = stmt.executeQuery();
while (resultSet.next())
{
String durableLockingID = resultSet.getString(1);
String userID = resultSet.getString(2);
int branchID = resultSet.getInt(3);
long timeStamp = resultSet.getLong(4);
boolean readOnly = resultSet.getBoolean(5);
LockArea area = makeLockArea(accessor, durableLockingID, userID, branchID, timeStamp, readOnly);
if (!handler.handleLockArea(area))
{
break;
}
}
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmt);
}
}
public void deleteLockArea(DBStoreAccessor accessor, String durableLockingID)
{
try
{
unlockWithoutCommit(accessor, durableLockingID);
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlDeleteLockArea, ReuseProbability.LOW);
stmt.setString(1, durableLockingID);
DBUtil.update(stmt, true);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
accessor.getConnection().commit();
}
catch (SQLException e)
{
throw new DBException(e);
}
}
public void updateLockArea(DBStoreAccessor accessor, LockArea area)
{
try
{
String areaID = area.getDurableLockingID();
unlockWithoutCommit(accessor, areaID);
insertLocks(accessor, areaID, area.getLocks());
accessor.getConnection().commit();
}
catch (SQLException e)
{
throw new DBException(e);
}
}
public void lock(DBStoreAccessor accessor, String durableLockingID, LockType type,
Collection<? extends Object> objectsToLock)
{
changeLocks(accessor, durableLockingID, type, objectsToLock, true);
}
public void unlock(DBStoreAccessor accessor, String durableLockingID, LockType type,
Collection<? extends Object> objectsToUnlock)
{
changeLocks(accessor, durableLockingID, type, objectsToUnlock, false);
}
public void unlock(DBStoreAccessor accessor, String durableLockingID)
{
try
{
unlockWithoutCommit(accessor, durableLockingID);
accessor.getConnection().commit();
}
catch (SQLException e)
{
throw new DBException(e);
}
}
private void unlockWithoutCommit(DBStoreAccessor accessor, String durableLockingID)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
try
{
stmt = statementCache.getPreparedStatement(sqlDeleteLocks, ReuseProbability.MEDIUM);
stmt.setString(1, durableLockingID);
DBUtil.update(stmt, false);
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
statementCache.releasePreparedStatement(stmt);
}
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
branchManager = store.getRepository().getBranchManager();
idHandler = store.getIDHandler();
IDBSchema schema = store.getDBSchema();
// Lock areas
lockAreas = schema.addTable("cdo_lock_areas");
lockAreasID = lockAreas.addField("id", DBType.VARCHAR);
lockAreasUser = lockAreas.addField("user_id", DBType.VARCHAR);
lockAreasBranch = lockAreas.addField("view_branch", DBType.INTEGER);
lockAreasTime = lockAreas.addField("view_time", DBType.BIGINT);
lockAreasReadOnly = lockAreas.addField("read_only", DBType.BOOLEAN);
lockAreas.addIndex(IDBIndex.Type.PRIMARY_KEY, lockAreasID);
lockAreas.addIndex(IDBIndex.Type.NON_UNIQUE, lockAreasUser);
// Locks
locks = schema.addTable("cdo_locks");
locksArea = locks.addField("area_id", DBType.VARCHAR);
locksObject = locks.addField("object_id", idHandler.getDBType());
locksGrade = locks.addField("lock_grade", DBType.INTEGER);
locks.addIndex(IDBIndex.Type.PRIMARY_KEY, locksArea, locksObject);
locks.addIndex(IDBIndex.Type.NON_UNIQUE, locksArea);
IDBStoreAccessor writer = store.getWriter(null);
Connection connection = writer.getConnection();
Statement statement = null;
try
{
statement = connection.createStatement();
store.getDBAdapter().createTable(lockAreas, statement);
store.getDBAdapter().createTable(locks, statement);
connection.commit();
}
catch (SQLException ex)
{
connection.rollback();
throw new DBException(ex);
}
finally
{
DBUtil.close(statement);
writer.release();
}
StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO "); //$NON-NLS-1$
builder.append(lockAreas);
builder.append("("); //$NON-NLS-1$
builder.append(lockAreasID);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasUser);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasBranch);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasTime);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasReadOnly);
builder.append(") VALUES (?, ?, ?, ?, ?)"); //$NON-NLS-1$
sqlInsertLockArea = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(lockAreasUser);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasBranch);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasTime);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasReadOnly);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(lockAreas);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(lockAreasID);
builder.append("=?"); //$NON-NLS-1$
sqlSelectLockArea = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(lockAreasID);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasUser);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasBranch);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasTime);
builder.append(","); //$NON-NLS-1$
builder.append(lockAreasReadOnly);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(lockAreas);
sqlSelectAllLockAreas = builder.toString();
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(lockAreasUser);
builder.append(" LIKE ?"); //$NON-NLS-1$
sqlSelectLockAreas = builder.toString();
builder = new StringBuilder();
builder.append("DELETE FROM "); //$NON-NLS-1$
builder.append(lockAreas);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(lockAreasID);
builder.append("=?"); //$NON-NLS-1$
sqlDeleteLockArea = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(locksObject);
builder.append(","); //$NON-NLS-1$
builder.append(locksGrade);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(locks);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(locksArea);
builder.append("=?"); //$NON-NLS-1$
sqlSelectLocks = builder.toString();
builder = new StringBuilder();
builder.append("SELECT "); //$NON-NLS-1$
builder.append(locksGrade);
builder.append(" FROM "); //$NON-NLS-1$
builder.append(locks);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(locksArea);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(locksObject);
builder.append("=?"); //$NON-NLS-1$
sqlSelectLock = builder.toString();
builder = new StringBuilder();
builder.append("INSERT INTO "); //$NON-NLS-1$
builder.append(locks);
builder.append("("); //$NON-NLS-1$
builder.append(locksArea);
builder.append(","); //$NON-NLS-1$
builder.append(locksObject);
builder.append(","); //$NON-NLS-1$
builder.append(locksGrade);
builder.append(") VALUES (?, ?, ?)"); //$NON-NLS-1$
sqlInsertLock = builder.toString();
builder = new StringBuilder();
builder.append("UPDATE "); //$NON-NLS-1$
builder.append(locks);
builder.append(" SET "); //$NON-NLS-1$
builder.append(locksGrade);
builder.append("=? "); //$NON-NLS-1$
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(locksArea);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(locksObject);
builder.append("=?"); //$NON-NLS-1$
sqlUpdateLock = builder.toString();
builder = new StringBuilder();
builder.append("DELETE FROM "); //$NON-NLS-1$
builder.append(locks);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(locksArea);
builder.append("=? AND "); //$NON-NLS-1$
builder.append(locksObject);
builder.append("=?"); //$NON-NLS-1$
sqlDeleteLock = builder.toString();
builder = new StringBuilder();
builder.append("DELETE FROM "); //$NON-NLS-1$
builder.append(locks);
builder.append(" WHERE "); //$NON-NLS-1$
builder.append(locksArea);
builder.append("=?"); //$NON-NLS-1$
sqlDeleteLocks = builder.toString();
}
private String getNextDurableLockingID(DBStoreAccessor accessor)
{
for (;;)
{
String durableLockingID = CDOLockUtil.createDurableLockingID();
try
{
getLockArea(accessor, durableLockingID); // Check uniqueness
// Not unique; try once more...
}
catch (LockAreaNotFoundException ex)
{
return durableLockingID;
}
}
}
private LockArea makeLockArea(DBStoreAccessor accessor, String durableLockingID, String userID, int branchID,
long timeStamp, boolean readOnly)
{
CDOBranchPoint branchPoint = branchManager.getBranch(branchID).getPoint(timeStamp);
Map<CDOID, LockGrade> lockMap = getLockMap(accessor, durableLockingID);
return CDOLockUtil.createLockArea(durableLockingID, userID, branchPoint, readOnly, lockMap);
}
private Map<CDOID, LockGrade> getLockMap(DBStoreAccessor accessor, String durableLockingID)
{
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmt = null;
ResultSet resultSet = null;
try
{
stmt = statementCache.getPreparedStatement(sqlSelectLocks, ReuseProbability.MEDIUM);
stmt.setString(1, durableLockingID);
resultSet = stmt.executeQuery();
Map<CDOID, LockGrade> lockMap = new HashMap<CDOID, LockGrade>();
while (resultSet.next())
{
CDOID id = idHandler.getCDOID(resultSet, 1);
int grade = resultSet.getInt(2);
lockMap.put(id, LockGrade.get(grade));
}
return lockMap;
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmt);
}
}
private void changeLocks(DBStoreAccessor accessor, String durableLockingID, LockType type,
Collection<? extends Object> keys, boolean on)
{
if (keys.isEmpty())
{
return;
}
IPreparedStatementCache statementCache = accessor.getStatementCache();
PreparedStatement stmtSelect = null;
PreparedStatement stmtInsertOrDelete = null;
PreparedStatement stmtUpdate = null;
ResultSet resultSet = null;
try
{
stmtSelect = statementCache.getPreparedStatement(sqlSelectLock, ReuseProbability.MEDIUM);
stmtSelect.setString(1, durableLockingID);
String sql = on ? sqlInsertLock : sqlDeleteLock;
stmtInsertOrDelete = statementCache.getPreparedStatement(sql, ReuseProbability.MEDIUM);
stmtInsertOrDelete.setString(1, durableLockingID);
stmtUpdate = statementCache.getPreparedStatement(sqlUpdateLock, ReuseProbability.MEDIUM);
stmtUpdate.setString(2, durableLockingID);
InternalLockManager lockManager = accessor.getStore().getRepository().getLockManager();
for (Object key : keys)
{
CDOID id = lockManager.getLockKeyID(key);
idHandler.setCDOID(stmtSelect, 2, id);
resultSet = stmtSelect.executeQuery();
LockGrade oldGrade = LockGrade.NONE;
if (resultSet.next())
{
oldGrade = LockGrade.get(resultSet.getInt(1));
}
LockGrade newGrade = oldGrade.getUpdated(type, on);
if (on && oldGrade == LockGrade.NONE)
{
idHandler.setCDOID(stmtInsertOrDelete, 2, id);
stmtInsertOrDelete.setInt(3, newGrade.getValue());
DBUtil.update(stmtInsertOrDelete, true);
}
else if (!on && newGrade == LockGrade.NONE)
{
idHandler.setCDOID(stmtInsertOrDelete, 2, id);
DBUtil.update(stmtInsertOrDelete, true);
}
else
{
stmtUpdate.setInt(1, newGrade.getValue());
idHandler.setCDOID(stmtUpdate, 3, id);
DBUtil.update(stmtUpdate, true);
}
}
accessor.getConnection().commit();
}
catch (SQLException e)
{
throw new DBException(e);
}
finally
{
DBUtil.close(resultSet);
statementCache.releasePreparedStatement(stmtUpdate);
statementCache.releasePreparedStatement(stmtInsertOrDelete);
statementCache.releasePreparedStatement(stmtSelect);
}
}
}