/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.core.manager.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Resource; import org.orcid.core.manager.WorkEntityCacheManager; import org.orcid.persistence.dao.WorkDao; import org.orcid.persistence.jpa.entities.MinimizedWorkEntity; import org.orcid.persistence.jpa.entities.WorkBaseEntity; import org.orcid.persistence.jpa.entities.WorkEntity; import org.orcid.persistence.jpa.entities.WorkLastModifiedEntity; import org.orcid.utils.ReleaseNameUtils; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; /** * * @author Will Simpson * */ public class WorkEntityCacheManagerImpl implements WorkEntityCacheManager { @Resource(name = "workLastModifiedCache") private Cache workLastModifiedCache; @Resource(name = "publicWorkLastModifiedCache") private Cache publicWorkLastModifiedCache; @Resource(name = "minimizedWorkEntityCache") private Cache minimizedWorkEntityCache; @Resource(name = "fullWorkEntityCache") private Cache fullWorkEntityCache; private String releaseName = ReleaseNameUtils.getReleaseName(); private LockerObjectsManager lockers = new LockerObjectsManager(); private LockerObjectsManager publicWorkLastModifiedListLockers = new LockerObjectsManager(); private LockerObjectsManager lockerMinimizedWork = new LockerObjectsManager(); private LockerObjectsManager lockerFullWork = new LockerObjectsManager(); private WorkDao workDao; public void setWorkDao(WorkDao workDao) { this.workDao = workDao; } @Override public List<WorkLastModifiedEntity> retrieveWorkLastModifiedList(String orcid, long profileLastModified) { Object key = new ProfileCacheKey(orcid, profileLastModified, releaseName); List<WorkLastModifiedEntity> workLastModifiedList = toWorkLastModifiedList(workLastModifiedCache.get(key)); if (workLastModifiedList == null) { try { synchronized (lockers.obtainLock(orcid)) { workLastModifiedList = toWorkLastModifiedList(workLastModifiedCache.get(key)); if (workLastModifiedList == null) { workLastModifiedList = workDao.getWorkLastModifiedList(orcid); workLastModifiedCache.put(new Element(key, workLastModifiedList)); } } } finally { lockers.releaseLock(orcid); } } return workLastModifiedList; } @Override public List<WorkLastModifiedEntity> retrievePublicWorkLastModifiedList(String orcid, long profileLastModified) { Object key = new ProfileCacheKey(orcid, profileLastModified, releaseName); List<WorkLastModifiedEntity> workLastModifiedList = toWorkLastModifiedList(publicWorkLastModifiedCache.get(key)); if (workLastModifiedList == null) { try { synchronized (publicWorkLastModifiedListLockers.obtainLock(orcid)) { workLastModifiedList = toWorkLastModifiedList(publicWorkLastModifiedCache.get(key)); if (workLastModifiedList == null) { workLastModifiedList = workDao.getPublicWorkLastModifiedList(orcid); publicWorkLastModifiedCache.put(new Element(key, workLastModifiedList)); } } } finally { publicWorkLastModifiedListLockers.releaseLock(orcid); } } return workLastModifiedList; } @Override public MinimizedWorkEntity retrieveMinimizedWork(long workId, long workLastModified) { Object key = new WorkCacheKey(workId, releaseName); MinimizedWorkEntity minimizedWorkEntity = toMinimizedWork(minimizedWorkEntityCache.get(key)); if (minimizedWorkEntity == null || minimizedWorkEntity.getLastModified().getTime() < workLastModified) { try { synchronized (lockerMinimizedWork.obtainLock(Long.toString(workId))) { minimizedWorkEntity = toMinimizedWork(minimizedWorkEntityCache.get(key)); if (minimizedWorkEntity == null || minimizedWorkEntity.getLastModified().getTime() < workLastModified) { minimizedWorkEntity = workDao.getMinimizedWorkEntity(workId); workDao.detach(minimizedWorkEntity); minimizedWorkEntityCache.put(new Element(key, minimizedWorkEntity)); } } } finally { lockerMinimizedWork.releaseLock(Long.toString(workId)); } } return minimizedWorkEntity; } /** * Retrieves a full WorkEntity * @param workId * @param workLastModified * @return a WorkEntity */ @Override public WorkEntity retrieveFullWork(String orcid, long workId, long workLastModified) { Object key = new WorkCacheKey(workId, releaseName); WorkEntity workEntity = (WorkEntity) toWorkBaseEntity(fullWorkEntityCache.get(key)); if (workEntity == null || workEntity.getLastModified().getTime() < workLastModified) { try { synchronized (lockerFullWork.obtainLock(Long.toString(workId))) { workEntity = (WorkEntity) toWorkBaseEntity(fullWorkEntityCache.get(key)); if (workEntity == null || workEntity.getLastModified().getTime() < workLastModified) { workEntity = workDao.getWork(orcid, workId); workDao.detach(workEntity); fullWorkEntityCache.put(new Element(key, workEntity)); } } } finally { lockerMinimizedWork.releaseLock(Long.toString(workId)); } } return workEntity; } /** * Fetches a list of minimised works - does this by checking cache and then * fetching all misses in one go from the DB. * * @param workIdsWithLastModified * @return */ @Override public <T extends WorkBaseEntity> List<T> retrieveWorkList(Map<Long, Date> workIdsWithLastModified, Cache workCache, LockerObjectsManager lockerObjectsManager, Function<List<Long>, List<T>> workRetriever) { WorkBaseEntity[] returnArray = new WorkBaseEntity[workIdsWithLastModified.size()]; List<Long> fetchList = new ArrayList<Long>(); Map<Long, Integer> fetchListIndexOrder = new LinkedHashMap<Long, Integer>(); int index = 0; for (Long workId : workIdsWithLastModified.keySet()) { // get works from the cache if we can Object key = new WorkCacheKey(workId, releaseName); WorkBaseEntity cachedWork = toWorkBaseEntity(workCache.get(key)); if (cachedWork == null || cachedWork.getLastModified().getTime() < workIdsWithLastModified.get(workId).getTime()) { fetchListIndexOrder.put(workId, index); fetchList.add(workId); } else { returnArray[index] = cachedWork; } index++; } // now fetch all the others that are *not* in the cache if (fetchList.size() > 0) { List<? extends WorkBaseEntity> refreshedWorks = workRetriever.apply(fetchList); for (WorkBaseEntity mWorkRefreshedFromDB : refreshedWorks) { Object key = new WorkCacheKey(mWorkRefreshedFromDB.getId(), releaseName); try { synchronized (lockerObjectsManager.obtainLock(Long.toString(mWorkRefreshedFromDB.getId()))) { // check cache again here to prevent race condition // since something could have updated while we were // fetching from DB // (or can we skip because new last modified is always // going to be after profile last modified as provided) WorkBaseEntity cachedWork = toWorkBaseEntity(workCache.get(key)); int returnListIndex = fetchListIndexOrder.get(mWorkRefreshedFromDB.getId()); if (cachedWork == null || cachedWork.getLastModified().getTime() < workIdsWithLastModified.get(mWorkRefreshedFromDB.getId()).getTime()) { workCache.put(new Element(key, mWorkRefreshedFromDB)); returnArray[returnListIndex] = mWorkRefreshedFromDB; } else { returnArray[returnListIndex] = cachedWork; } } } finally { lockerObjectsManager.releaseLock(Long.toString(mWorkRefreshedFromDB.getId())); } } } @SuppressWarnings("unchecked") List<T> results = (List<T>) Arrays.asList(returnArray); return results; } @Override public List<MinimizedWorkEntity> retrieveMinimizedWorks(String orcid, long profileLastModified) { Map<Long, Date> workIdsWithLastModified = retrieveWorkLastModifiedMap(orcid, profileLastModified); return retrieveWorkList(workIdsWithLastModified, minimizedWorkEntityCache, lockerMinimizedWork, idList -> workDao.getMinimizedWorkEntities(idList)); } @Override public List<MinimizedWorkEntity> retrievePublicMinimizedWorks(String orcid, long profileLastModified) { List<WorkLastModifiedEntity> workLastModifiedList = retrievePublicWorkLastModifiedList(orcid, profileLastModified); Map<Long, Date> workIdsWithLastModified = workLastModifiedList.stream().collect(Collectors.toMap( WorkLastModifiedEntity::getId, WorkLastModifiedEntity::getLastModified, (u, v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); return this.retrieveWorkList(workIdsWithLastModified, minimizedWorkEntityCache, lockerMinimizedWork, idList -> workDao.getMinimizedWorkEntities(idList)); } @Override public List<WorkEntity> retrieveFullWorks(String orcid, long profileLastModified) { Map<Long, Date> workIdsWithLastModified = retrieveWorkLastModifiedMap(orcid, profileLastModified); return retrieveWorkList(workIdsWithLastModified, fullWorkEntityCache, lockerFullWork, idList -> workDao.getWorkEntities(idList)); } private Map<Long, Date> retrieveWorkLastModifiedMap(String orcid, long profileLastModified) { List<WorkLastModifiedEntity> workLastModifiedList = retrieveWorkLastModifiedList(orcid, profileLastModified); // @formatter:off Map<Long, Date> workIdsWithLastModified = workLastModifiedList.stream().collect(Collectors.toMap( WorkLastModifiedEntity::getId, WorkLastModifiedEntity::getLastModified, (u, v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new)); // @formatter:off return workIdsWithLastModified; } private MinimizedWorkEntity toMinimizedWork(Element element) { return (MinimizedWorkEntity) (element != null ? element.getObjectValue() : null); } private WorkBaseEntity toWorkBaseEntity(Element element) { return (WorkBaseEntity) (element != null ? element.getObjectValue() : null); } @SuppressWarnings("unchecked") private List<WorkLastModifiedEntity> toWorkLastModifiedList(Element element) { return (List<WorkLastModifiedEntity>) (element != null ? element.getObjectValue() : null); } }