2012-09-30 17:15:01 -03:00
/ *
2022-03-17 18:47:34 -03:00
* Copyright [ 2022 ] [ wisemapping ]
2022-03-15 10:30:18 -03:00
*
* Licensed under WiseMapping Public License , Version 1 . 0 ( the " License " ) .
* It is basically the Apache License , Version 2 . 0 ( the " License " ) plus the
* " powered by wisemapping " text requirement on every single page ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the license at
*
* http : //www.wisemapping.org/license
*
* 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 .
* /
2012-09-30 17:15:01 -03:00
package com.wisemapping.service ;
import com.wisemapping.exceptions.AccessDeniedSecurityException ;
import com.wisemapping.exceptions.LockException ;
2022-03-22 08:35:47 -03:00
import com.wisemapping.exceptions.SessionExpiredException ;
2012-09-30 17:15:01 -03:00
import com.wisemapping.model.CollaborationRole ;
import com.wisemapping.model.Mindmap ;
2012-11-10 17:19:28 -03:00
import com.wisemapping.model.User ;
2012-09-30 17:15:01 -03:00
import org.apache.log4j.Logger ;
import org.jetbrains.annotations.NotNull ;
2022-03-22 08:35:47 -03:00
import org.jetbrains.annotations.Nullable ;
2012-09-30 17:15:01 -03:00
import java.util.* ;
import java.util.concurrent.ConcurrentHashMap ;
class LockManagerImpl implements LockManager {
2022-03-22 08:35:47 -03:00
private static final int ONE_MINUTE_MILLISECONDS = 1000 * 60 ;
private final Map < Integer , LockInfo > lockInfoByMapId ;
private final static Timer expirationTimer = new Timer ( ) ;
2021-12-24 18:03:23 -08:00
final private static Logger logger = Logger . getLogger ( LockManagerImpl . class ) ;
2012-09-30 17:15:01 -03:00
@Override
public boolean isLocked ( @NotNull Mindmap mindmap ) {
return this . getLockInfo ( mindmap ) ! = null ;
}
@Override
public LockInfo getLockInfo ( @NotNull Mindmap mindmap ) {
return lockInfoByMapId . get ( mindmap . getId ( ) ) ;
}
@Override
2012-11-13 21:01:04 -03:00
public LockInfo updateExpirationTimeout ( @NotNull Mindmap mindmap , @NotNull User user ) {
2012-10-04 20:28:59 -03:00
if ( ! this . isLocked ( mindmap ) ) {
2012-09-30 17:15:01 -03:00
throw new IllegalStateException ( " Lock lost for map. No update possible. " ) ;
}
2012-10-04 20:28:59 -03:00
final LockInfo result = this . getLockInfo ( mindmap ) ;
2012-11-14 20:35:09 -03:00
if ( ! result . getUser ( ) . identityEquality ( user ) ) {
2012-11-10 17:19:28 -03:00
throw new IllegalStateException ( " Could not update map lock timeout if you are not the locking user. User: " + result . getUser ( ) + " , " + user ) ;
2012-10-04 20:28:59 -03:00
}
result . updateTimeout ( ) ;
result . updateTimestamp ( mindmap ) ;
2012-11-13 21:01:04 -03:00
logger . debug ( " Timeout updated for: " + mindmap . getId ( ) ) ;
2012-10-04 20:28:59 -03:00
return result ;
2012-09-30 17:15:01 -03:00
}
2013-04-07 12:27:45 -03:00
@Override
public void unlockAll ( @NotNull final User user ) throws LockException , AccessDeniedSecurityException {
final Set < Integer > mapIds = lockInfoByMapId . keySet ( ) ;
for ( final Integer mapId : mapIds ) {
final LockInfo lockInfo = lockInfoByMapId . get ( mapId ) ;
if ( lockInfo . getUser ( ) . identityEquality ( user ) ) {
unlock ( mapId ) ;
}
}
}
2012-09-30 17:15:01 -03:00
@Override
2012-11-10 17:19:28 -03:00
public void unlock ( @NotNull Mindmap mindmap , @NotNull User user ) throws LockException , AccessDeniedSecurityException {
2012-09-30 17:15:01 -03:00
if ( isLocked ( mindmap ) & & ! isLockedBy ( mindmap , user ) ) {
throw new LockException ( " Lock can be only revoked by the locker. " ) ;
}
if ( ! mindmap . hasPermissions ( user , CollaborationRole . EDITOR ) ) {
2022-03-15 10:30:18 -03:00
throw new AccessDeniedSecurityException ( mindmap . getId ( ) , user ) ;
2012-09-30 17:15:01 -03:00
}
this . unlock ( mindmap . getId ( ) ) ;
}
private void unlock ( int mapId ) {
logger . debug ( " Unlock map id: " + mapId ) ;
lockInfoByMapId . remove ( mapId ) ;
}
@Override
2012-11-10 17:19:28 -03:00
public boolean isLockedBy ( @NotNull Mindmap mindmap , @NotNull User collaborator ) {
2012-09-30 17:15:01 -03:00
boolean result = false ;
final LockInfo lockInfo = this . getLockInfo ( mindmap ) ;
2012-11-14 20:33:42 -03:00
if ( lockInfo ! = null & & lockInfo . getUser ( ) . identityEquality ( collaborator ) ) {
2012-09-30 17:15:01 -03:00
result = true ;
}
return result ;
}
2012-11-13 21:01:04 -03:00
2012-11-14 20:44:59 -03:00
@Override
public long generateSession ( ) {
return System . nanoTime ( ) ;
}
2012-10-04 20:28:59 -03:00
@NotNull
2022-03-23 10:56:57 -03:00
@Override
public LockInfo lock ( @NotNull Mindmap mindmap , @NotNull User user , long session ) throws LockException {
2012-09-30 17:15:01 -03:00
if ( isLocked ( mindmap ) & & ! isLockedBy ( mindmap , user ) ) {
2022-03-23 10:56:57 -03:00
throw LockException . createLockLost ( mindmap , user , this ) ;
2012-09-30 17:15:01 -03:00
}
2012-10-04 20:28:59 -03:00
LockInfo result = lockInfoByMapId . get ( mindmap . getId ( ) ) ;
if ( result ! = null ) {
2012-09-30 17:15:01 -03:00
// Update timeout only...
logger . debug ( " Update timestamp: " + mindmap . getId ( ) ) ;
2012-11-13 21:01:04 -03:00
updateExpirationTimeout ( mindmap , user ) ;
2013-04-07 12:27:45 -03:00
// result.setSession(session);
2012-09-30 17:15:01 -03:00
} else {
logger . debug ( " Lock map id: " + mindmap . getId ( ) ) ;
2012-10-04 20:28:59 -03:00
result = new LockInfo ( user , mindmap , session ) ;
lockInfoByMapId . put ( mindmap . getId ( ) , result ) ;
2012-09-30 17:15:01 -03:00
}
2012-10-04 20:28:59 -03:00
return result ;
2012-09-30 17:15:01 -03:00
}
2012-10-04 20:28:59 -03:00
public LockManagerImpl ( ) {
2022-03-16 23:16:34 -03:00
lockInfoByMapId = new ConcurrentHashMap < > ( ) ;
2012-10-04 20:28:59 -03:00
expirationTimer . schedule ( new TimerTask ( ) {
@Override
public void run ( ) {
2022-03-22 08:35:47 -03:00
synchronized ( this ) {
logger . debug ( " Lock expiration scheduler started. Current locks: " + lockInfoByMapId . keySet ( ) ) ;
// Search for expired sessions and remove them ....
lockInfoByMapId .
keySet ( ) .
stream ( ) .
filter ( mapId - > lockInfoByMapId . get ( mapId ) . isExpired ( ) ) .
forEach ( mapId - > unlock ( mapId ) ) ;
}
2022-03-16 23:16:34 -03:00
2012-10-04 20:28:59 -03:00
}
} , ONE_MINUTE_MILLISECONDS , ONE_MINUTE_MILLISECONDS ) ;
2012-09-30 17:15:01 -03:00
}
2012-10-04 20:28:59 -03:00
2022-03-22 08:35:47 -03:00
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 ( ) ) ;
}
}
2012-09-30 17:15:01 -03:00
}