/* * This is a common dao with basic CRUD operations and is not limited to any * persistent layer implementation * * Copyright (C) 2011 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.common.cache.dao.impl; import com.google.inject.Inject; import com.google.inject.name.Named; import com.smartitengineering.dao.common.CommonReadDao; import com.smartitengineering.dao.common.CommonWriteDao; import com.smartitengineering.dao.common.cache.CacheServiceProvider; import com.smartitengineering.dao.common.cache.Lock; import com.smartitengineering.dao.common.cache.Mutex; import com.smartitengineering.dao.common.cache.dao.CacheKeyGenearator; import com.smartitengineering.dao.common.cache.dao.CacheableDao; import com.smartitengineering.dao.common.cache.impl.CacheAPIFactory; import com.smartitengineering.dao.common.queryparam.QueryParameter; import com.smartitengineering.domain.PersistentDTO; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author imyousuf */ public class CacheableDaoImpl<Template extends PersistentDTO, IdType extends Serializable, CacheKey extends Serializable> implements CacheableDao<Template, IdType, CacheKey> { @Inject @Named("primaryCacheableWriteDao") private CommonWriteDao<Template> primaryWriteDao; @Inject @Named("primaryCacheableReadDao") private CommonReadDao<Template, IdType> primaryReadDao; @Inject private CacheServiceProvider<CacheKey, Template> cacheProvider; @Inject private CacheKeyGenearator<Template, IdType, CacheKey> cacheKeyGenearator; protected transient final Logger logger = LoggerFactory.getLogger(getClass()); protected final Mutex<CacheKey> mutex = CacheAPIFactory.<CacheKey>getMutex(); @Override public CacheKeyGenearator<Template, IdType, CacheKey> getCacheKeyGenearator() { return cacheKeyGenearator; } @Override public void setCacheKeyGenearator(CacheKeyGenearator<Template, IdType, CacheKey> cacheKeyGenearator) { this.cacheKeyGenearator = cacheKeyGenearator; } @Override public CacheServiceProvider<CacheKey, Template> getCacheProvider() { return cacheProvider; } @Override public void setCacheProvider(CacheServiceProvider<CacheKey, Template> cacheProvider) { this.cacheProvider = cacheProvider; } @Override public CommonReadDao<Template, IdType> getPrimaryReadDao() { return primaryReadDao; } @Override public void setPrimaryReadDao(CommonReadDao<Template, IdType> primaryReadDao) { this.primaryReadDao = primaryReadDao; } @Override public CommonWriteDao<Template> getPrimaryWriteDao() { return primaryWriteDao; } @Override public void setPrimaryWriteDao(CommonWriteDao<Template> primaryWriteDao) { this.primaryWriteDao = primaryWriteDao; } @Override public Set<Template> getAll() { return primaryReadDao.getAll(); } @Override public Set<Template> getByIds(List<IdType> pids) { if (pids == null || pids.isEmpty()) { return Collections.emptySet(); } List<CacheKey> keys = new ArrayList<CacheKey>(pids.size()); Map<CacheKey, IdType> keyIds = new HashMap<CacheKey, IdType>(keys.size()); for (IdType pid : pids) { CacheKey key = cacheKeyGenearator.generateKeyFromId(pid); if (key == null) { continue; } keys.add(key); keyIds.put(key, pid); } if (keys == null || keys.isEmpty()) { return Collections.emptySet(); } Map<CacheKey, Template> results = new HashMap<CacheKey, Template>(keys.size()); List<CacheKey> missedKeys = new ArrayList<CacheKey>(keys); results.putAll(cacheProvider.retrieveFromCache(missedKeys)); for (CacheKey id : results.keySet()) { missedKeys.remove(id); } Map<CacheKey, Lock<CacheKey>> locks = new HashMap<CacheKey, Lock<CacheKey>>(missedKeys.size()); for (CacheKey missedId : missedKeys) { boolean attained = false; while (!attained) { try { locks.put(missedId, mutex.acquire(missedId)); attained = true; } catch (Exception ex) { logger.warn("Could not acquire lock for user!"); } } } results.putAll(cacheProvider.retrieveFromCache(missedKeys)); for (CacheKey id : results.keySet()) { if (missedKeys.remove(id)) { mutex.release(locks.get(id)); } } List<IdType> missedIds = new ArrayList<IdType>(); for (CacheKey missedKey : missedKeys) { missedIds.add(keyIds.get(missedKey)); } Set<Template> fromSource = primaryReadDao.getByIds(missedIds); for (Template template : fromSource) { final CacheKey key = cacheKeyGenearator.generateKeyFromObject(template); putToCache(template, key); results.put(key, template); } for (CacheKey id : missedKeys) { mutex.release(locks.get(id)); } LinkedHashSet<Template> resultSet = new LinkedHashSet<Template>(results.size()); for (CacheKey id : keys) { Template user = results.get(id); if (user != null) { resultSet.add(user); } } return resultSet; } @Override public Template getById(IdType id) { CacheKey key = cacheKeyGenearator.generateKeyFromId(id); if (key == null) { return primaryReadDao.getById(id); } Template template = cacheProvider.retrieveFromCache(key); if (template != null) { return template; } else { try { Lock<CacheKey> lock = mutex.acquire(key); template = cacheProvider.retrieveFromCache(key); if (template != null) { if (logger.isInfoEnabled()) { logger.info("Cache hit for " + key); } return template; } template = primaryReadDao.getById(id); if (template != null) { putToCache(template, key); } mutex.release(lock); } catch (Exception ex) { logger.warn("Could not do cache lookup!", ex); } return template; } } @Override public Template getSingle(List<QueryParameter> query) { return primaryReadDao.getSingle(query); } @Override public List<Template> getList(List<QueryParameter> query) { return primaryReadDao.getList(query); } @Override public <OtherTemplate> OtherTemplate getOther(List<QueryParameter> query) { return primaryReadDao.<OtherTemplate>getOther(query); } @Override public <OtherTemplate> List<OtherTemplate> getOtherList(List<QueryParameter> query) { return primaryReadDao.<OtherTemplate>getOtherList(query); } @Override public Template getSingle(QueryParameter... query) { return primaryReadDao.getSingle(query); } @Override public List<Template> getList(QueryParameter... query) { return primaryReadDao.getList(query); } @Override public <OtherTemplate> OtherTemplate getOther(QueryParameter... query) { return primaryReadDao.<OtherTemplate>getOther(query); } @Override public <OtherTemplate> List<OtherTemplate> getOtherList(QueryParameter... query) { return primaryReadDao.<OtherTemplate>getOtherList(query); } @Override public void save(Template... states) { primaryWriteDao.save(states); } @Override public void update(Template... states) { try { primaryWriteDao.update(states); for (Template template : states) { final CacheKey key = cacheKeyGenearator.generateKeyFromObject(template); if (key == null) { logger.warn("CacheKey null on update!"); continue; } Lock<CacheKey> lock = mutex.acquire(key); try { expireFromCache(key); } finally { mutex.release(lock); } } } catch (InterruptedException ex) { logger.info("Could not invalidate cache on update!", ex); throw new RuntimeException(ex); } catch (RuntimeException exception) { logger.info("Could not update thus did not invalidate cache!", exception); throw exception; } } @Override public void delete(Template... states) { try { primaryWriteDao.delete(states); for (Template template : states) { final CacheKey key = cacheKeyGenearator.generateKeyFromObject(template); if (key == null) { logger.warn("CacheKey null on delete!"); continue; } Lock<CacheKey> lock = mutex.acquire(key); try { expireFromCache(key); } finally { mutex.release(lock); } } } catch (InterruptedException ex) { logger.info("Could not invalidate cache on delete!", ex); throw new RuntimeException(ex); } catch (RuntimeException exception) { logger.info("Could not delete thus did not invalidate cache!", exception); throw exception; } } protected void putToCache(Template template, CacheKey key) { cacheProvider.putToCache(key, template); } protected void expireFromCache(CacheKey key) { cacheProvider.expireFromCache(key); if (logger.isInfoEnabled()) { logger.info("Confirming removal of cache key " + key + ": " + cacheProvider.containsKey(key)); } } }