/* * This is a common dao with basic CRUD operations and is not limited to any * persistent layer implementation * * Copyright (C) 2010 Imran M Yousuf (imyousuf@smartitengineering.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package com.smartitengineering.dao.impl.hbase.spi.impl; import com.google.inject.Inject; import com.smartitengineering.dao.impl.hbase.spi.AsyncExecutorService; import com.smartitengineering.dao.impl.hbase.spi.Callback; import com.smartitengineering.dao.impl.hbase.spi.LockAttainer; import com.smartitengineering.dao.impl.hbase.spi.SchemaInfoProvider; import com.smartitengineering.domain.PersistentDTO; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.RowLock; import org.apache.hadoop.hbase.util.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author imyousuf */ public class LockAttainerImpl<T extends PersistentDTO, IdType> implements LockAttainer<T, IdType> { private final ConcurrentHashMap<Key<T>, MonitorableThreadLocal<Map<String, RowLock>>> locksCache = new ConcurrentHashMap<Key<T>, MonitorableThreadLocal<Map<String, RowLock>>>(); protected final Logger logger = LoggerFactory.getLogger(getClass()); @Inject private SchemaInfoProvider<T, IdType> infoProvider; @Inject private AsyncExecutorService executorService; private final ScheduledExecutorService cleanupExecutor; { cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); cleanupExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { Iterator<Entry<Key<T>, MonitorableThreadLocal<Map<String, RowLock>>>> entries = locksCache.entrySet().iterator(); while (entries.hasNext()) { Entry<Key<T>, MonitorableThreadLocal<Map<String, RowLock>>> entry = entries.next(); if (entry.getKey().getInstance() == null || entry.getValue().container.size() <= 0) { entries.remove(); } } } }, 5, 10, TimeUnit.MINUTES); } @Override public Map<String, RowLock> getLock(final T instance, String... tables) { final Key key = new Key(instance); if (locksCache.containsKey(key) && locksCache.get(key).get() != null) { return Collections.unmodifiableMap(locksCache.get(key).get()); } logger.info("Not found in cache so trying to retrieve!"); final Map<String, Future<RowLock>> map = new LinkedHashMap<String, Future<RowLock>>(); if (tables == null) { tables = new String[]{infoProvider.getMainTableName()}; } if (executorService != null) { for (String table : tables) { Future<RowLock> future = executorService.executeAsynchronously(table, new Callback<RowLock>() { @Override public RowLock call(HTableInterface tableInterface) throws Exception { final byte[] rowIdFromRow = infoProvider.getRowIdFromRow(instance); if (logger.isDebugEnabled()) { logger.debug("Attaining lock for " + Bytes.toString(rowIdFromRow)); } try { return tableInterface.lockRow(rowIdFromRow); } catch (Exception ex) { logger.warn(ex.getMessage(), ex); throw ex; } } }); logger.debug("RECEIVED FUTURE Lock"); map.put(table, future); } } final Map<String, RowLock> lockMap = new HashMap<String, RowLock>(map.size()); for (Entry<String, Future<RowLock>> lock : map.entrySet()) { try { lockMap.put(lock.getKey(), lock.getValue().get(infoProvider.getWaitTime(), infoProvider.getUnit())); } catch (Exception ex) { logger.error("Error trying to get lock!", ex); throw new RuntimeException(ex); } } MonitorableThreadLocal<Map<String, RowLock>> old = locksCache.putIfAbsent(key, new MonitorableThreadLocal<Map<String, RowLock>>()); if (logger.isDebugEnabled() && old == null) { logger.debug("OLD THREAD LOCAL is NULL for " + key); } MonitorableThreadLocal<Map<String, RowLock>> local = locksCache.get(key); if (local == null) { logger.error("THREAD LOCAL NULL for " + key + ", " + lockMap); if (logger.isDebugEnabled()) { logger.debug("Locks " + locksCache); for (Map.Entry<Key<T>, MonitorableThreadLocal<Map<String, RowLock>>> lockes : locksCache.entrySet()) { logger.debug("Key: " + lockes.getKey() + ", Value: " + lockes.getValue() + ", " + key.equals(lockes.getKey())); } } } local.set(lockMap); return lockMap; } @Override public boolean evictFromCache(T instance) { final Key key = new Key(instance); final MonitorableThreadLocal<Map<String, RowLock>> local = locksCache.get(key); local.remove(); return local != null; } @Override public void putLock(T instance, Map<String, RowLock> locks) { Key key = new Key(instance); locksCache.putIfAbsent(key, new MonitorableThreadLocal<Map<String, RowLock>>()); MonitorableThreadLocal<Map<String, RowLock>> local = locksCache.get(key); local.set(locks); } @Override public boolean unlockAndEvictFromCache(T instance) { if (logger.isDebugEnabled()) { logger.debug("Instance to remove " + instance.getClass() + " " + instance); logger.debug("Cache " + locksCache.getClass() + " " + locksCache); } final Key key = new Key(instance); MonitorableThreadLocal<Map<String, RowLock>> local = locksCache.get(key); if (local == null) { logger.info("No thread local container for key!"); return false; } Map<String, RowLock> locks = local.get(); if (locks == null) { logger.info("No locks in cache!"); return false; } else { local.remove(); for (final Entry<String, RowLock> lock : locks.entrySet()) { executorService.executeAsynchronously(lock.getKey(), new Callback<Void>() { @Override public Void call(HTableInterface tableInterface) throws Exception { final RowLock lockVal = lock.getValue(); if (logger.isInfoEnabled()) { logger.info("Unlocking row: " + lockVal.getLockId()); } tableInterface.unlockRow(lockVal); return null; } }); } return false; } } private static class MonitorableThreadLocal<T> { private final AtomicLong counter = new AtomicLong(0); private final ThreadLocal<Long> localId = new ThreadLocal<Long>(); private final ConcurrentHashMap<Long, T> container = new ConcurrentHashMap<Long, T>(); public T get() { Long currentId = getCurrentId(); return container.get(currentId); } protected Long getCurrentId() { Long currentId = localId.get(); if (currentId == null) { currentId = counter.incrementAndGet(); localId.set(currentId); } return currentId; } public void set(T data) { Long currentId = getCurrentId(); container.put(currentId, data); } public void remove() { Long currentId = getCurrentId(); container.remove(currentId); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final MonitorableThreadLocal<T> other = (MonitorableThreadLocal<T>) obj; if (this.container != other.container && (this.container == null || !this.container.equals(other.container))) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 47 * hash + (this.container != null ? this.container.hashCode() : 0); return hash; } } private static class Key<T extends PersistentDTO> { private final T instance; protected final Logger logger = LoggerFactory.getLogger(getClass()); public Key(T instance) { this.instance = instance; } public T getInstance() { return this.instance; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Key<T> other = (Key<T>) obj; T thisObj = this.instance; T thatObj = other.instance; if (thisObj == null && thatObj == null) { return true; } else if (thisObj != null && thatObj != null) { return thisObj.equals(thatObj); } else { return false; } } @Override public int hashCode() { int hash = 5; return hash; } @Override public String toString() { final T vInstance = instance; return "Key{" + "instance=" + (instance != null && vInstance != null && vInstance.getId() != null ? vInstance. getId().toString() : null) + '}'; } } }