001/**
002 * Copyright 2014 Tampere University of Technology, Pori Department
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 *   http://www.apache.org/licenses/LICENSE-2.0
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package core.tut.pori.utils;
017
018import java.util.HashMap;
019import java.util.concurrent.locks.ReentrantLock;
020
021import org.apache.log4j.Logger;
022
023import core.tut.pori.users.UserIdentity;
024
025/**
026 * Acquire a lock per-thread, per-user identity, multiple instance of this class will be entirely independent, and will NOT block each-other
027 * UserIdentityLock userIdentityLock = UserIdentityLock();
028 * 
029 * Usage:
030 * userIdentityLock.acquire(userId);
031 * try{
032 *  ...
033 * }finally{
034 *  userIdentityLock.release(userId);
035 * }
036 */
037public class UserIdentityLock {
038  private static final Logger LOGGER = Logger.getLogger(UserIdentityLock.class);
039  private HashMap<Long, LockCounter> _locks = new HashMap<>();
040
041  /**
042   * Blocks until the lock for the given user identity has been acquired.
043   * 
044   * @param userId
045   * @throws IllegalArgumentException on invalid user identity
046   */
047  public void acquire(UserIdentity userId) throws IllegalArgumentException {
048    if(!UserIdentity.isValid(userId)){
049      throw new IllegalArgumentException("Invalid user identity.");
050    }
051    
052    Long userIdValue = userId.getUserId();
053    ReentrantLock lock = null;
054    synchronized (_locks) {
055      LockCounter counter = _locks.get(userIdValue);
056      if(counter == null){
057        LOGGER.debug("Creating new lock for user, id: "+userIdValue);
058        _locks.put(userIdValue, (counter = new LockCounter()));
059      }else if(counter._lock.isHeldByCurrentThread()){
060        LOGGER.debug("Already held by current thread.");
061        return;
062      }else{
063        ++counter._count;
064      }
065      lock = counter._lock;
066    }
067    
068    LOGGER.debug("Attempting to acquire lock for user, id: "+userIdValue);
069    lock.lock();
070    LOGGER.debug("Lock acquired for user, id: "+userIdValue);
071  }
072  
073  /**
074   * Release the lock for the given user identity.
075   * 
076   * @param userId
077   * @throws IllegalArgumentException
078   */
079  public void release(UserIdentity userId) throws IllegalArgumentException {
080    if(!UserIdentity.isValid(userId)){
081      throw new IllegalArgumentException("Invalid user identity.");
082    }
083    
084    Long userIdValue = userId.getUserId();
085    synchronized (_locks) {
086      LockCounter counter = _locks.get(userIdValue);
087      if(counter == null){
088        throw new IllegalArgumentException("No lock for user, id: "+userIdValue);
089      }
090      
091      LOGGER.debug("Releasing lock for user, id: "+userIdValue);
092      counter._lock.unlock();
093      
094      if(--counter._count < 1){
095        LOGGER.debug("The last was lock released, removing lock for user, id: "+userIdValue);
096        _locks.remove(userIdValue);
097      }
098    }
099  }
100  
101  /**
102   * Used to count the number of locks acquired.
103   */
104  private class LockCounter {
105    public int _count = 1;
106    public ReentrantLock _lock = new ReentrantLock();
107  } // class LockCounter
108}