| /* |
| * Copyright (c) 2011-2013, 2015 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.id.CDOIDUtil; |
| 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.common.protocol.CDODataInput; |
| import org.eclipse.emf.cdo.common.protocol.CDODataOutput; |
| import org.eclipse.emf.cdo.server.db.IIDHandler; |
| 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.IDBDatabase; |
| import org.eclipse.net4j.db.IDBDatabase.RunnableWithSchema; |
| import org.eclipse.net4j.db.IDBPreparedStatement; |
| import org.eclipse.net4j.db.IDBPreparedStatement.ReuseProbability; |
| 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 org.eclipse.net4j.util.om.monitor.OMMonitor; |
| |
| import java.io.IOException; |
| import java.sql.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| /** |
| * @author Eike Stepper |
| */ |
| public class DurableLockingManager extends Lifecycle |
| { |
| private static final String LOCK_AREAS = "CDO_LOCK_AREAS"; |
| |
| private static final String LOCK_AREAS_ID = "ID"; |
| |
| private static final String LOCK_AREAS_USER_ID = "USER_ID"; |
| |
| private static final String LOCK_AREAS_VIEW_BRANCH = "VIEW_BRANCH"; |
| |
| private static final String LOCK_AREAS_VIEW_TIME = "VIEW_TIME"; |
| |
| private static final String LOCK_AREAS_READ_ONLY = "READ_ONLY"; |
| |
| private static final String LOCKS = "CDO_LOCKS"; |
| |
| private static final String LOCKS_AREA_ID = "AREA_ID"; |
| |
| private static final String LOCKS_OBJECT_ID = "OBJECT_ID"; |
| |
| private static final String LOCKS_LOCK_GRADE = "LOCK_GRADE"; |
| |
| private DBStore store; |
| |
| private InternalCDOBranchManager branchManager; |
| |
| private IIDHandler idHandler; |
| |
| private IDBTable lockAreasTable; |
| |
| private IDBTable locksTable; |
| |
| private String sqlInsertLockArea; |
| |
| private String sqlSelectLockArea; |
| |
| private String sqlSelectAllLockAreas; |
| |
| private String sqlSelectLockAreas; |
| |
| private String sqlDeleteLockArea; |
| |
| private String sqlDeleteLockAreas; |
| |
| 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) |
| { |
| 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) |
| { |
| } |
| } |
| |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsertLockArea, ReuseProbability.LOW); |
| |
| try |
| { |
| 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 |
| { |
| DBUtil.close(stmt); |
| } |
| |
| if (!locks.isEmpty()) |
| { |
| insertLocks(accessor, durableLockingID, locks); |
| } |
| |
| commit(accessor); |
| |
| return CDOLockUtil.createLockArea(durableLockingID, userID, branchPoint, readOnly, locks); |
| } |
| |
| private void insertLocks(DBStoreAccessor accessor, String durableLockingID, Map<CDOID, LockGrade> locks) |
| { |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlInsertLock, ReuseProbability.MEDIUM); |
| |
| try |
| { |
| 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 |
| { |
| DBUtil.close(stmt); |
| } |
| } |
| |
| public LockArea getLockArea(DBStoreAccessor accessor, String durableLockingID) throws LockAreaNotFoundException |
| { |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectLockArea, ReuseProbability.MEDIUM); |
| ResultSet resultSet = null; |
| |
| try |
| { |
| 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); |
| DBUtil.close(stmt); |
| } |
| } |
| |
| public void getLockAreas(DBStoreAccessor accessor, String userIDPrefix, Handler handler) |
| { |
| IDBPreparedStatement stmt = null; |
| ResultSet resultSet = null; |
| |
| try |
| { |
| if (userIDPrefix.length() == 0) |
| { |
| stmt = accessor.getDBConnection().prepareStatement(sqlSelectAllLockAreas, ReuseProbability.MEDIUM); |
| } |
| else |
| { |
| stmt = accessor.getDBConnection().prepareStatement(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); |
| DBUtil.close(stmt); |
| } |
| } |
| |
| public void deleteLockArea(DBStoreAccessor accessor, String durableLockingID) |
| { |
| unlockWithoutCommit(accessor, durableLockingID); |
| |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlDeleteLockArea, ReuseProbability.LOW); |
| |
| try |
| { |
| stmt.setString(1, durableLockingID); |
| DBUtil.update(stmt, true); |
| } |
| catch (SQLException e) |
| { |
| throw new DBException(e); |
| } |
| finally |
| { |
| DBUtil.close(stmt); |
| } |
| |
| commit(accessor); |
| } |
| |
| public void updateLockArea(DBStoreAccessor accessor, LockArea area) |
| { |
| String areaID = area.getDurableLockingID(); |
| unlockWithoutCommit(accessor, areaID); |
| insertLocks(accessor, areaID, area.getLocks()); |
| commit(accessor); |
| } |
| |
| 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) |
| { |
| unlockWithoutCommit(accessor, durableLockingID); |
| commit(accessor); |
| } |
| |
| private void unlockWithoutCommit(DBStoreAccessor accessor, String durableLockingID) |
| { |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlDeleteLocks, ReuseProbability.MEDIUM); |
| |
| try |
| { |
| stmt.setString(1, durableLockingID); |
| DBUtil.update(stmt, false); |
| } |
| catch (SQLException e) |
| { |
| throw new DBException(e); |
| } |
| finally |
| { |
| DBUtil.close(stmt); |
| } |
| } |
| |
| @Override |
| protected void doActivate() throws Exception |
| { |
| super.doActivate(); |
| |
| branchManager = store.getRepository().getBranchManager(); |
| idHandler = store.getIDHandler(); |
| IDBDatabase database = store.getDatabase(); |
| |
| // Lock areas |
| lockAreasTable = database.getSchema().getTable(LOCK_AREAS); |
| if (lockAreasTable == null) |
| { |
| database.updateSchema(new RunnableWithSchema() |
| { |
| public void run(IDBSchema schema) |
| { |
| lockAreasTable = schema.addTable(LOCK_AREAS); |
| lockAreasTable.addField(LOCK_AREAS_ID, DBType.VARCHAR, true); |
| lockAreasTable.addField(LOCK_AREAS_USER_ID, DBType.VARCHAR); |
| lockAreasTable.addField(LOCK_AREAS_VIEW_BRANCH, DBType.INTEGER); |
| lockAreasTable.addField(LOCK_AREAS_VIEW_TIME, DBType.BIGINT); |
| lockAreasTable.addField(LOCK_AREAS_READ_ONLY, DBType.BOOLEAN); |
| lockAreasTable.addIndex(IDBIndex.Type.PRIMARY_KEY, LOCK_AREAS_ID); |
| lockAreasTable.addIndex(IDBIndex.Type.NON_UNIQUE, LOCK_AREAS_USER_ID); |
| } |
| }); |
| } |
| |
| sqlInsertLockArea = "INSERT INTO " + LOCK_AREAS + "(" + LOCK_AREAS_ID + "," + LOCK_AREAS_USER_ID + "," |
| + LOCK_AREAS_VIEW_BRANCH + "," + LOCK_AREAS_VIEW_TIME + "," + LOCK_AREAS_READ_ONLY + ") VALUES (?, ?, ?, ?, ?)"; |
| sqlSelectLockArea = "SELECT " + LOCK_AREAS_USER_ID + "," + LOCK_AREAS_VIEW_BRANCH + "," + LOCK_AREAS_VIEW_TIME + "," |
| + LOCK_AREAS_READ_ONLY + " FROM " + LOCK_AREAS + " WHERE " + LOCK_AREAS_ID + "=?"; |
| sqlSelectAllLockAreas = "SELECT " + LOCK_AREAS_ID + "," + LOCK_AREAS_USER_ID + "," + LOCK_AREAS_VIEW_BRANCH + "," |
| + LOCK_AREAS_VIEW_TIME + "," + LOCK_AREAS_READ_ONLY + " FROM " + LOCK_AREAS; |
| sqlSelectLockAreas = sqlSelectAllLockAreas + " WHERE " + LOCK_AREAS_USER_ID + " LIKE ?"; |
| sqlDeleteLockArea = "DELETE FROM " + LOCK_AREAS + " WHERE " + LOCK_AREAS_ID + "=?"; |
| sqlDeleteLockAreas = "DELETE FROM " + LOCK_AREAS + " WHERE EXISTS (SELECT * FROM " + LOCKS + " WHERE " + LOCKS + "." |
| + LOCKS_AREA_ID + "=" + LOCK_AREAS + "." + LOCK_AREAS_ID + ")"; |
| |
| // Locks |
| locksTable = database.getSchema().getTable(LOCKS); |
| if (locksTable == null) |
| { |
| database.updateSchema(new RunnableWithSchema() |
| { |
| public void run(IDBSchema schema) |
| { |
| locksTable = schema.addTable(LOCKS); |
| locksTable.addField(LOCKS_AREA_ID, DBType.VARCHAR, true); |
| locksTable.addField(LOCKS_OBJECT_ID, idHandler.getDBType(), store.getIDColumnLength(), true); |
| locksTable.addField(LOCKS_LOCK_GRADE, DBType.INTEGER); |
| locksTable.addIndex(IDBIndex.Type.PRIMARY_KEY, LOCKS_AREA_ID, LOCKS_OBJECT_ID); |
| locksTable.addIndex(IDBIndex.Type.NON_UNIQUE, LOCKS_AREA_ID); |
| } |
| }); |
| } |
| |
| sqlSelectLocks = "SELECT " + LOCKS_OBJECT_ID + "," + LOCKS_LOCK_GRADE + " FROM " + LOCKS + " WHERE " + LOCKS_AREA_ID |
| + "=?"; |
| sqlSelectLock = "SELECT " + LOCKS_LOCK_GRADE + " FROM " + LOCKS + " WHERE " + LOCKS_AREA_ID + "=? AND " |
| + LOCKS_OBJECT_ID + "=?"; |
| sqlInsertLock = "INSERT INTO " + LOCKS + "(" + LOCKS_AREA_ID + "," + LOCKS_OBJECT_ID + "," + LOCKS_LOCK_GRADE |
| + ") VALUES (?, ?, ?)"; |
| sqlUpdateLock = "UPDATE " + LOCKS + " SET " + LOCKS_LOCK_GRADE + "=? " + " WHERE " + LOCKS_AREA_ID + "=? AND " |
| + LOCKS_OBJECT_ID + "=?"; |
| sqlDeleteLock = "DELETE FROM " + LOCKS + " WHERE " + LOCKS_AREA_ID + "=? AND " + LOCKS_OBJECT_ID + "=?"; |
| sqlDeleteLocks = "DELETE FROM " + LOCKS + " WHERE " + LOCKS_AREA_ID + "=?"; |
| } |
| |
| 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) |
| { |
| IDBPreparedStatement stmt = accessor.getDBConnection().prepareStatement(sqlSelectLocks, ReuseProbability.MEDIUM); |
| ResultSet resultSet = null; |
| |
| try |
| { |
| stmt.setString(1, durableLockingID); |
| resultSet = stmt.executeQuery(); |
| |
| Map<CDOID, LockGrade> lockMap = CDOIDUtil.createMap(); |
| 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); |
| DBUtil.close(stmt); |
| } |
| } |
| |
| private void changeLocks(DBStoreAccessor accessor, String durableLockingID, LockType type, |
| Collection<? extends Object> keys, boolean on) |
| { |
| if (keys.isEmpty()) |
| { |
| return; |
| } |
| |
| String sql = on ? sqlInsertLock : sqlDeleteLock; |
| |
| IDBPreparedStatement stmtSelect = accessor.getDBConnection().prepareStatement(sqlSelectLock, |
| ReuseProbability.MEDIUM); |
| IDBPreparedStatement stmtInsertOrDelete = accessor.getDBConnection().prepareStatement(sql, ReuseProbability.MEDIUM); |
| IDBPreparedStatement stmtUpdate = accessor.getDBConnection().prepareStatement(sqlUpdateLock, |
| ReuseProbability.MEDIUM); |
| ResultSet resultSet = null; |
| |
| try |
| { |
| stmtSelect.setString(1, durableLockingID); |
| stmtInsertOrDelete.setString(1, durableLockingID); |
| stmtUpdate.setString(2, durableLockingID); |
| |
| InternalLockManager lockManager = accessor.getStore().getRepository().getLockingManager(); |
| 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.getDBConnection().commit(); |
| } |
| catch (SQLException e) |
| { |
| throw new DBException(e); |
| } |
| finally |
| { |
| DBUtil.close(resultSet); |
| DBUtil.close(stmtUpdate); |
| DBUtil.close(stmtInsertOrDelete); |
| DBUtil.close(stmtSelect); |
| } |
| } |
| |
| public void rawExport(Connection connection, CDODataOutput out, long fromCommitTime, long toCommitTime) |
| throws IOException |
| { |
| DBUtil.serializeTable(out, connection, lockAreasTable, null, null); |
| DBUtil.serializeTable(out, connection, locksTable, null, null); |
| } |
| |
| public void rawImport(Connection connection, CDODataInput in, long fromCommitTime, long toCommitTime, |
| OMMonitor monitor) throws IOException |
| { |
| monitor.begin(4); |
| |
| try |
| { |
| // Delete all non-empty lock areas |
| DBUtil.update(connection, sqlDeleteLockAreas); |
| monitor.worked(); |
| |
| DBUtil.deserializeTable(in, connection, lockAreasTable, monitor.fork()); |
| |
| DBUtil.clearTable(connection, locksTable); |
| monitor.worked(); |
| |
| DBUtil.deserializeTable(in, connection, locksTable, monitor.fork()); |
| } |
| finally |
| { |
| monitor.done(); |
| } |
| } |
| |
| private static void commit(DBStoreAccessor accessor) |
| { |
| try |
| { |
| accessor.getDBConnection().commit(); |
| } |
| catch (SQLException ex) |
| { |
| throw new DBException(ex); |
| } |
| } |
| } |