package gov.nysenate.openleg.service.entity.committee.data;
import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gov.nysenate.openleg.dao.base.LimitOffset;
import gov.nysenate.openleg.dao.base.SortOrder;
import gov.nysenate.openleg.dao.entity.committee.data.CommitteeDao;
import gov.nysenate.openleg.model.base.SessionYear;
import gov.nysenate.openleg.model.cache.CacheEvictEvent;
import gov.nysenate.openleg.model.cache.CacheEvictIdEvent;
import gov.nysenate.openleg.model.cache.CacheWarmEvent;
import gov.nysenate.openleg.model.cache.ContentCache;
import gov.nysenate.openleg.model.entity.*;
import gov.nysenate.openleg.model.sobi.SobiFragment;
import gov.nysenate.openleg.service.base.data.CachingService;
import gov.nysenate.openleg.service.entity.committee.event.CommitteeUpdateEvent;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.MemoryUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class CachedCommitteeDataService implements CommitteeDataService, CachingService<CommitteeSessionId> {
private static final Logger logger = LoggerFactory.getLogger(CachedCommitteeDataService.class);
@Autowired private CacheManager cacheManager;
@Autowired private CommitteeDao committeeDao;
@Autowired private EventBus eventBus;
@Value("${committee.cache.size}") private long committeeCacheSizeMb;
private Cache committeeCache;
@PostConstruct
private void init() {
setupCaches();
eventBus.register(this);
}
@PreDestroy
private void cleanUp() {
evictCaches();
cacheManager.removeCache(ContentCache.COMMITTEE.name());
}
/** --- Cache Management --- */
/**
* {@inheritDoc}
*/
@Override
public List<Ehcache> getCaches() {
return Arrays.asList(committeeCache);
}
/** {@inheritDoc} */
@Override
public void setupCaches() {
committeeCache = new Cache(new CacheConfiguration().name(ContentCache.COMMITTEE.name())
.eternal(true)
.maxBytesLocalHeap(committeeCacheSizeMb, MemoryUnit.MEGABYTES)
.sizeOfPolicy(defaultSizeOfPolicy()));
cacheManager.addCache(committeeCache);
committeeCache.setMemoryStoreEvictionPolicy(new CommitteeCacheEvictionPolicy());
}
/** {@inheritDoc} */
@Override
public void evictContent(CommitteeSessionId committeeSessionId) {
committeeCache.remove(committeeSessionId);
}
/** {@inheritDoc} */
@Override
@Subscribe
public synchronized void handleCacheEvictEvent(CacheEvictEvent evictEvent) {
if (evictEvent.affects(ContentCache.COMMITTEE)) {
evictCaches();
}
}
/** {@inheritDoc} */
@Subscribe
@Override
public void handleCacheEvictIdEvent(CacheEvictIdEvent<CommitteeSessionId> evictIdEvent) {
if (evictIdEvent.affects(ContentCache.COMMITTEE)) {
evictContent(evictIdEvent.getContentId());
}
}
/** {@inheritDoc} */
@Override
public void warmCaches() {
evictCaches();
logger.info("Warming up committee cache.");
getCommitteeList(Chamber.SENATE, LimitOffset.ALL);
getCommitteeList(Chamber.ASSEMBLY, LimitOffset.ALL);
}
/** {@inheritDoc} */
@Override
public void handleCacheWarmEvent(CacheWarmEvent warmEvent) {
if (warmEvent.affects(ContentCache.COMMITTEE)) {
warmCaches();
}
}
/** --- Committee Data Services --- */
/**
* {@inheritDoc}
* @param committeeSessionId
*/
@Override
public Committee getCommittee(CommitteeSessionId committeeSessionId) throws CommitteeNotFoundEx {
if (committeeSessionId == null) {
throw new IllegalArgumentException("committeeSessionId cannot be null!");
}
try {
return getCommitteeHistory(committeeSessionId).get(0);
} catch (IndexOutOfBoundsException ex) {
throw new CommitteeNotFoundEx(committeeSessionId, ex);
}
}
/** {@inheritDoc} */
@Override
public Committee getCommittee(CommitteeVersionId committeeVersionId) throws CommitteeNotFoundEx {
if (committeeVersionId == null) {
throw new IllegalArgumentException("committeeVersionId cannot be null!");
}
LocalDateTime refDate = committeeVersionId.getReferenceDate();
for (Committee committee : getCommitteeHistory(committeeVersionId)) {
if ( committee.getCreated().equals(refDate) ||
committee.getCreated().isBefore(refDate) &&
(committee.getReformed()==null || committee.getReformed().isAfter(refDate)) ) {
return committee;
}
}
throw new CommitteeNotFoundEx(committeeVersionId, null);
}
/**
* {@inheritDoc}
*/
@Override
public List<CommitteeId> getCommitteeIds() {
return committeeDao.getCommitteeList();
}
@Override
public List<SessionYear> getEligibleYears() {
return committeeDao.getEligibleYears();
}
@Override
public List<CommitteeSessionId> getAllCommitteeSessionIds() {
return committeeDao.getAllSessionIds();
}
/** {@inheritDoc} */
@Override
public List<Committee> getCommitteeList(Chamber chamber, SessionYear sessionYear, LimitOffset limitOffset) {
if (chamber == null) {
throw new IllegalArgumentException("Chamber cannot be null!");
}
List<Committee> committeeList = new ArrayList<>();
getCommitteeIds().stream()
.filter(committeeId -> committeeId.getChamber().equals(chamber))
.map(committeeId -> new CommitteeSessionId(committeeId, sessionYear))
.forEach(committeeSessionId -> {
try {
committeeList.add(getCommittee(committeeSessionId));
} catch (CommitteeNotFoundEx ignored) {}
});
return LimitOffset.limitList(committeeList, limitOffset != null ? limitOffset : LimitOffset.ALL);
}
/** {@inheritDoc} */
@Override
public int getCommitteeListCount(Chamber chamber, SessionYear sessionYear) {
return getCommitteeList(chamber, sessionYear, LimitOffset.ALL).size();
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
public List<Committee> getCommitteeHistory(CommitteeSessionId committeeSessionId,
LimitOffset limitOffset, SortOrder order) throws CommitteeNotFoundEx {
if (committeeSessionId ==null) {
throw new IllegalArgumentException("CommitteeSessionId cannot be null!");
}
List<Committee> committeeHistory;
Element element = committeeCache.get(committeeSessionId);
if (element != null) {
logger.debug("Committee cache hit for {}", committeeSessionId);
committeeHistory = (List<Committee>) element.getObjectValue();
}
else {
try {
committeeHistory = committeeDao.getCommitteeHistory(committeeSessionId);
committeeCache.put(new Element(committeeSessionId, committeeHistory));
logger.debug("Added committee history {} to cache", committeeSessionId);
}
catch (EmptyResultDataAccessException ex){
throw new CommitteeNotFoundEx(committeeSessionId, ex);
}
}
// The dao provides the result already in DESC order by created date
if (order != null && order.equals(SortOrder.ASC)) {
committeeHistory = Lists.reverse(committeeHistory);
}
if (limitOffset != null && !limitOffset.equals(LimitOffset.ALL)) {
committeeHistory = LimitOffset.limitList(committeeHistory, limitOffset);
}
return committeeHistory;
}
/** {@inheritDoc}
* @param committeeSessionId*/
@Override
public int getCommitteeHistoryCount(CommitteeSessionId committeeSessionId) {
try {
return getCommitteeHistory(committeeSessionId).size();
} catch (CommitteeNotFoundEx ex) {
return 0;
}
}
/** {@inheritDoc} */
@Override
public void saveCommittee(Committee committee, SobiFragment sobiFragment) {
if(committee==null) {
throw new IllegalArgumentException("Committee cannot be null.");
}
committeeDao.updateCommittee(committee, sobiFragment);
committeeCache.remove(committee.getSessionId());
eventBus.post(new CommitteeUpdateEvent(committee, LocalDateTime.now()));
}
/** {@inheritDoc} */
@Override
public void deleteCommittee(CommitteeId committeeId) {
if(committeeId==null) {
throw new IllegalArgumentException("CommitteeId cannot be null!");
}
committeeDao.deleteCommittee(committeeId);
}
}