Clean up lock implementation

This commit is contained in:
Paulo Gustavo Veiga
2022-03-23 23:32:19 -03:00
parent b8eadb7533
commit d380b97938
9 changed files with 68 additions and 183 deletions

View File

@@ -27,16 +27,22 @@ import java.util.Calendar;
public class LockInfo {
final private User user;
private Calendar timeout;
private long session;
private static final int EXPIRATION_MIN = 30;
private long timestamp = -1;
private long previousTimestamp;
public LockInfo(@NotNull User user, @NotNull Mindmap mindmap, long session) {
public int getMapId() {
return mapId;
}
public void setMapId(int mapId) {
this.mapId = mapId;
}
private int mapId;
public LockInfo(@NotNull User user, @NotNull Mindmap mindmap) {
this.user = user;
this.mapId = mindmap.getId();
this.updateTimeout();
this.updateTimestamp(mindmap);
this.session = session;
}
public User getUser() {
@@ -54,33 +60,12 @@ public class LockInfo {
}
public long getSession() {
return session;
}
public void setSession(long session) {
this.session = session;
}
public long getTimestamp() {
return timestamp;
}
public long getPreviousTimestamp() {
return previousTimestamp;
}
public void updateTimestamp(@NotNull Mindmap mindmap) {
this.previousTimestamp = this.timestamp;
this.timestamp = mindmap.getLastModificationTime().getTimeInMillis();
}
@Override
public String toString() {
return "LockInfo{" +
"user=" + user +
", session=" + session +
", timestamp=" + timestamp +
", timeout=" + timeout +
", mapId=" + mapId +
'}';
}
}

View File

@@ -21,7 +21,6 @@ package com.wisemapping.service;
import com.wisemapping.exceptions.AccessDeniedSecurityException;
import com.wisemapping.exceptions.LockException;
import com.wisemapping.exceptions.SessionExpiredException;
import com.wisemapping.exceptions.WiseMappingException;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import org.jetbrains.annotations.NotNull;
@@ -31,19 +30,14 @@ public interface LockManager {
LockInfo getLockInfo(@NotNull Mindmap mindmap);
LockInfo updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull User user);
void unlock(@NotNull Mindmap mindmap, @NotNull User user) throws LockException, AccessDeniedSecurityException;
void unlockAll(@NotNull User user) throws LockException, AccessDeniedSecurityException;
boolean isLockedBy(@NotNull Mindmap mindmap, @NotNull User collaborator);
boolean isLockedBy(@NotNull Mindmap mindmap, @NotNull User user);
long generateSession();
@NotNull
LockInfo lock(@NotNull Mindmap mindmap, @NotNull User user, long session) throws LockException;
long verifyAndUpdateLock(@NotNull Mindmap mindmap, @NotNull User user, long session, long timestamp) throws
LockException, SessionExpiredException;
LockInfo lock(@NotNull Mindmap mindmap, @NotNull User user) throws LockException;
}

View File

@@ -20,7 +20,6 @@ package com.wisemapping.service;
import com.wisemapping.exceptions.AccessDeniedSecurityException;
import com.wisemapping.exceptions.LockException;
import com.wisemapping.exceptions.SessionExpiredException;
import com.wisemapping.model.CollaborationRole;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
@@ -47,23 +46,6 @@ class LockManagerImpl implements LockManager {
return lockInfoByMapId.get(mindmap.getId());
}
@Override
public LockInfo updateExpirationTimeout(@NotNull Mindmap mindmap, @NotNull User user) {
if (!this.isLocked(mindmap)) {
throw new IllegalStateException("Lock lost for map. No update possible.");
}
final LockInfo result = this.getLockInfo(mindmap);
if (!result.getUser().identityEquality(user)) {
throw new IllegalStateException("Could not update map lock timeout if you are not the locking user. User:" + result.getUser() + ", " + user);
}
result.updateTimeout();
result.updateTimestamp(mindmap);
logger.debug("Timeout updated for:" + mindmap.getId());
return result;
}
@Override
public void unlockAll(@NotNull final User user) throws LockException, AccessDeniedSecurityException {
final Set<Integer> mapIds = lockInfoByMapId.keySet();
@@ -77,14 +59,7 @@ class LockManagerImpl implements LockManager {
@Override
public void unlock(@NotNull Mindmap mindmap, @NotNull User user) throws LockException, AccessDeniedSecurityException {
if (isLocked(mindmap) && !isLockedBy(mindmap, user)) {
throw new LockException("Lock can be only revoked by the locker.");
}
if (!mindmap.hasPermissions(user, CollaborationRole.EDITOR)) {
throw new AccessDeniedSecurityException(mindmap.getId(), user);
}
verifyHasLock(mindmap, user);
this.unlock(mindmap.getId());
}
@@ -111,25 +86,38 @@ class LockManagerImpl implements LockManager {
@NotNull
@Override
public LockInfo lock(@NotNull Mindmap mindmap, @NotNull User user, long session) throws LockException {
public LockInfo lock(@NotNull Mindmap mindmap, @NotNull User user) throws LockException {
if (isLocked(mindmap) && !isLockedBy(mindmap, user)) {
throw LockException.createLockLost(mindmap, user, this);
}
// Do I need to create a new lock ?
LockInfo result = lockInfoByMapId.get(mindmap.getId());
if (result != null) {
// Update timeout only...
logger.debug("Update timestamp:" + mindmap.getId());
updateExpirationTimeout(mindmap, user);
// result.setSession(session);
} else {
logger.debug("Lock map id:" + mindmap.getId());
result = new LockInfo(user, mindmap, session);
if (result == null) {
logger.debug("Creating new lock for map id:" + mindmap.getId());
result = new LockInfo(user, mindmap);
lockInfoByMapId.put(mindmap.getId(), result);
}
// Update timestamp ...
logger.debug("Updating timeout:" + result);
result.updateTimeout();
return result;
}
private void verifyHasLock(@NotNull Mindmap mindmap, @NotNull User user) throws LockException, AccessDeniedSecurityException {
// Only editor can have lock ...
if (!mindmap.hasPermissions(user, CollaborationRole.EDITOR)) {
throw new AccessDeniedSecurityException(mindmap.getId(), user);
}
// Is the lock assigned to the user ...
if (isLocked(mindmap) && !isLockedBy(mindmap, user)) {
throw LockException.createLockLost(mindmap, user, this);
}
}
public LockManagerImpl() {
lockInfoByMapId = new ConcurrentHashMap<>();
expirationTimer.schedule(new TimerTask() {
@@ -149,46 +137,4 @@ class LockManagerImpl implements LockManager {
}
}, ONE_MINUTE_MILLISECONDS, ONE_MINUTE_MILLISECONDS);
}
public long verifyAndUpdateLock(@NotNull Mindmap mindmap, @NotNull User user, @Nullable long session, @NotNull long timestamp) throws LockException, SessionExpiredException {
synchronized (this) {
// Could the map be updated ?
verifyLock(mindmap, user, session, timestamp);
// Update timestamp for lock ...
final LockInfo lockInfo = this.updateExpirationTimeout(mindmap, user);
return lockInfo.getTimestamp();
}
}
private void verifyLock(@NotNull Mindmap mindmap, @NotNull User user, long session, long timestamp) throws LockException, SessionExpiredException {
// The lock was lost, reclaim as the ownership of it.
final boolean lockLost = this.isLocked(mindmap);
if (!lockLost) {
this.lock(mindmap, user, session);
}
final LockInfo lockInfo = this.getLockInfo(mindmap);
if (lockInfo.getUser().identityEquality(user)) {
long savedTimestamp = mindmap.getLastModificationTime().getTimeInMillis();
final boolean outdated = savedTimestamp > timestamp;
if (lockInfo.getSession() == session) {
// Timestamp might not be returned to the client. This try to cover this case, ignoring the client timestamp check.
final User lastEditor = mindmap.getLastEditor();
boolean editedBySameUser = lastEditor == null || user.identityEquality(lastEditor);
if (outdated && !editedBySameUser) {
throw new SessionExpiredException("Map has been updated by " + (lastEditor.getEmail()) + ",Timestamp:" + timestamp + "," + savedTimestamp + ", User:" + lastEditor.getId() + ":" + user.getId() + ",Mail:'" + lastEditor.getEmail() + "':'" + user.getEmail(), lastEditor);
}
} else if (outdated) {
logger.warn("Sessions:" + session + ":" + lockInfo.getSession() + ",Timestamp: " + timestamp + ": " + savedTimestamp);
// @Todo: Temporally disabled to unblock save action. More research needed.
// throw new MultipleSessionsOpenException("Sessions:" + session + ":" + lockInfo.getSession() + ",Timestamp: " + timestamp + ": " + savedTimestamp);
}
} else {
throw new SessionExpiredException("Different Users.", lockInfo.getUser());
}
}
}