/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of the License at the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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. */ package org.apereo.portal.concurrency.locking; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apereo.portal.concurrency.IEntityLock; import org.apereo.portal.concurrency.LockingException; import org.apereo.portal.utils.SmartCache; /** * In-memory store for <code>IEntityLocks</code>. */ public class MemoryEntityLockStore implements IEntityLockStore { private static IEntityLockStore singleton; // The lock store is a Map that contains a SmartCache for each entity type: private Map lockCache; /** MemoryEntityLockStore constructor comment. */ public MemoryEntityLockStore() { super(); initializeCache(); } /** * Adds this IEntityLock to the store. * * @param lock */ public void add(IEntityLock lock) throws LockingException { primAdd(lock, lock.getExpirationTime()); } /** * Deletes this IEntityLock from the store. * * @param lock */ public void delete(IEntityLock lock) throws LockingException { Map m = getLockCache(lock.getEntityType()); synchronized (m) { m.remove(getCacheKey(lock)); } } public void deleteAll() { initializeCache(); } /** * Deletes the expired IEntityLocks from the underlying store. * * @param expiration java.util.Date */ public void deleteExpired(java.util.Date expiration) throws LockingException { // let SmartCache handle it. } /** * Returns an IEntityLock[] based on the params, any or all of which may be null. A null param * means any value, so <code>find(myType,myKey,null,null,null)</code> will return all <code> * IEntityLocks</code> for myType and myKey. * * @return org.apereo.portal.concurrency.locking.IEntityLock[] * @param entityType Class * @param entityKey String * @param lockType Integer - so we can accept a null value. * @param expiration Date * @param lockOwner String * @exception LockingException - wraps an Exception specific to the store. */ public IEntityLock[] find( Class entityType, String entityKey, Integer lockType, java.util.Date expiration, String lockOwner) throws LockingException { List locks = new ArrayList(); Map cache = null; Collection caches = null; Iterator cacheIterator = null; Iterator cacheKeyIterator = null; Iterator keyIterator = null; IEntityLock lock = null; if (entityType == null) { caches = getLockCache().values(); } else { caches = new ArrayList(1); caches.add(getLockCache(entityType)); } cacheIterator = caches.iterator(); while (cacheIterator.hasNext()) { cache = (Map) cacheIterator.next(); cacheKeyIterator = cache.keySet().iterator(); List keys = new ArrayList(); // Synchronize on the cache only while collecting its keys. There is some // exposure here. synchronized (cache) { while (cacheKeyIterator.hasNext()) { keys.add(cacheKeyIterator.next()); } } keyIterator = keys.iterator(); while (keyIterator.hasNext()) { lock = getLockFromCache(keyIterator.next(), cache); if ((lock != null) && ((entityKey == null) || (entityKey.equals(lock.getEntityKey()))) && ((lockType == null) || (lockType.intValue() == lock.getLockType())) && ((lockOwner == null) || (lockOwner.equals(lock.getLockOwner()))) && ((expiration == null) || (expiration.equals(lock.getExpirationTime())))) { locks.add(lock); } } } return ((IEntityLock[]) locks.toArray(new IEntityLock[locks.size()])); } /** * Returns this lock if it exists in the store. * * @param lock * @return IEntityLock */ public IEntityLock find(IEntityLock lock) throws LockingException { IEntityLock foundLock = null; Map m = getLockCache(lock.getEntityType()); foundLock = getLockFromCache(getCacheKey(lock), m); if (foundLock != null) { if (lock.getLockType() != foundLock.getLockType() || !lock.getExpirationTime().equals(foundLock.getExpirationTime())) { foundLock = null; } } return foundLock; } /** * Returns an IEntityLock[] containing unexpired locks, based on the params, any or all of which * may be null EXCEPT FOR <code>expiration</code>. A null param means any value, so <code> * find(expir,myType,myKey,null,null)</code> will return all <code>IEntityLocks</code> for * myType and myKey unexpired as of <code>expir</code>. * * @param expiration Date * @param entityType Class * @param entityKey String * @param lockType Integer - so we can accept a null value. * @param lockOwner String * @exception LockingException - wraps an Exception specific to the store. */ public IEntityLock[] findUnexpired( java.util.Date expiration, Class entityType, String entityKey, Integer lockType, String lockOwner) throws LockingException { IEntityLock[] locks = find(entityType, entityKey, lockType, null, lockOwner); List lockAL = new ArrayList(locks.length); for (int i = 0; i < locks.length; i++) { if (locks[i].getExpirationTime().after(expiration)) { lockAL.add(locks[i]); } } return ((IEntityLock[]) lockAL.toArray(new IEntityLock[lockAL.size()])); } /** @param lock org.apereo.portal.concurrency.locking.IEntityLock */ private String getCacheKey(IEntityLock lock) { return lock.getEntityKey() + lock.getLockOwner(); } /** @return java.util.Map */ private java.util.Map getLockCache() { return lockCache; } /** @return java.util.Map */ private synchronized Map getLockCache(Class type) { Map m = (Map) getLockCache().get(type); if (m == null) { m = new SmartCache(); getLockCache().put(type, m); } return m; } private IEntityLock getLockFromCache(Object cacheKey, Map cache) { synchronized (cache) { return (IEntityLock) cache.get(cacheKey); } } private void initializeCache() { lockCache = new HashMap(10); } /** @param newLockCache java.util.Map */ private void setLockCache(java.util.Map newLockCache) { lockCache = newLockCache; } /** @return org.apereo.portal.concurrency.locking.IEntityLockStore */ public static synchronized IEntityLockStore singleton() { if (singleton == null) { singleton = new MemoryEntityLockStore(); } return singleton; } /** * @param lock * @param newExpiration */ public void update(IEntityLock lock, java.util.Date newExpiration) throws LockingException { update(lock, newExpiration, null); } /** * Make sure the store has a reference to the lock, and then add the lock to refresh the * SmartCache wrapper. * * @param lock org.apereo.portal.concurrency.locking.IEntityLock * @param newExpiration java.util.Date * @param newLockType Integer */ public void update(IEntityLock lock, java.util.Date newExpiration, Integer newLockType) throws LockingException { if (find(lock) == null) { throw new LockingException("Problem updating " + lock + " : not found in store."); } primAdd(lock, newExpiration); } /** * Adds this IEntityLock to the store. * * @param lock * @param expiration */ private void primAdd(IEntityLock lock, Date expiration) throws LockingException { long now = System.currentTimeMillis(); long willExpire = expiration.getTime(); long cacheIntervalSecs = (willExpire - now) / 1000; if (cacheIntervalSecs > 0) { SmartCache sc = (SmartCache) getLockCache(lock.getEntityType()); synchronized (sc) { sc.put(getCacheKey(lock), lock, (cacheIntervalSecs)); } } // Else the lock has already expired. } }