package gov.nysenate.openleg.service.agenda.data; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import gov.nysenate.openleg.dao.agenda.data.AgendaDao; import gov.nysenate.openleg.dao.base.SortOrder; import gov.nysenate.openleg.model.agenda.Agenda; import gov.nysenate.openleg.model.agenda.AgendaId; import gov.nysenate.openleg.model.agenda.AgendaNotFoundEx; import gov.nysenate.openleg.model.cache.CacheEvictIdEvent; import gov.nysenate.openleg.model.sobi.SobiFragment; import gov.nysenate.openleg.model.cache.CacheEvictEvent; import gov.nysenate.openleg.model.cache.CacheWarmEvent; import gov.nysenate.openleg.service.agenda.event.AgendaUpdateEvent; import gov.nysenate.openleg.service.base.data.CachingService; import gov.nysenate.openleg.model.cache.ContentCache; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Ehcache; 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.cache.ehcache.EhCacheCache; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; @Service public class CachedAgendaDataService implements AgendaDataService, CachingService<AgendaId> { private static final Logger logger = LoggerFactory.getLogger(CachedAgendaDataService.class); @Autowired private CacheManager cacheManager; @Autowired private AgendaDao agendaDao; @Autowired private EventBus eventBus; @Value("${agenda.cache.size}") private long agendaCacheSizeMb; private EhCacheCache agendaCache; @PostConstruct private void init() { eventBus.register(this); setupCaches(); } @PreDestroy private void cleanUp() { evictCaches(); cacheManager.removeCache(ContentCache.AGENDA.name()); } /** --- CachingService implementation --- */ @Override public List<Ehcache> getCaches() { return Arrays.asList(agendaCache.getNativeCache()); } /** {@inheritDoc} */ @Override public void setupCaches() { Cache cache = new Cache(new CacheConfiguration().name(ContentCache.AGENDA.name()) .eternal(true) .maxBytesLocalHeap(agendaCacheSizeMb, MemoryUnit.MEGABYTES) .sizeOfPolicy(defaultSizeOfPolicy())); cacheManager.addCache(cache); this.agendaCache = new EhCacheCache(cache); } /** {@inheritDoc} */ @Override @Subscribe public void handleCacheEvictEvent(CacheEvictEvent evictEvent) { if (evictEvent.affects(ContentCache.AGENDA)) { evictCaches(); } } /** {@inheritDoc} */ @Subscribe @Override public void handleCacheEvictIdEvent(CacheEvictIdEvent<AgendaId> evictIdEvent) { if (evictIdEvent.affects(ContentCache.AGENDA)) { evictContent(evictIdEvent.getContentId()); } } @Override public void evictContent(AgendaId agendaId) { agendaCache.evict(agendaId); } /** * Pre-load the agenda cache by first clearing its current contents and then requesting every agenda * in the past 4 years. */ public void warmCaches() { evictCaches(); logger.info("Warming up agenda cache."); int year = LocalDate.now().getYear(); for (int i = 3; i >= 0; i--) { logger.info("Fetching agendas for year {}", (year - i)); getAgendaIds(year - i, SortOrder.ASC).forEach(a -> getAgenda(a)); } logger.info("Done warming up agenda cache."); } /** {@inheritDoc} */ @Override @Subscribe public void handleCacheWarmEvent(CacheWarmEvent warmEvent) { if (warmEvent.affects(ContentCache.AGENDA)) { warmCaches(); } } /** {@inheritDoc} */ @Override public Agenda getAgenda(AgendaId agendaId) throws AgendaNotFoundEx { if (agendaId == null) { throw new IllegalArgumentException("AgendaId cannot be null."); } try { Agenda agenda = (agendaCache.get(agendaId) != null) ? (Agenda) agendaCache.get(agendaId).get() : null; if (agenda == null) { logger.debug("Fetching agenda {}", agendaId); agenda = agendaDao.getAgenda(agendaId); agendaCache.put(agendaId, agenda); } return agenda; } catch (EmptyResultDataAccessException ex) { throw new AgendaNotFoundEx(agendaId); } } /** {@inheritDoc} */ @Override public Agenda getAgenda(LocalDate weekOf) throws AgendaNotFoundEx { try { return agendaDao.getAgenda(weekOf); } catch (EmptyResultDataAccessException ex) { throw new AgendaNotFoundEx(weekOf); } } /** {@inheritDoc} */ @Override public List<AgendaId> getAgendaIds(int year, SortOrder idOrder) { return agendaDao.getAgendaIds(year, idOrder); } /** {@inheritDoc} */ @Override public void saveAgenda(Agenda agenda, SobiFragment sobiFragment, boolean postUpdateEvent) { if (agenda == null) { throw new IllegalArgumentException("Agenda cannot be null when saving."); } logger.debug("Persisting agenda {}", agenda.getId()); agendaDao.updateAgenda(agenda, sobiFragment); agendaCache.put(agenda.getId(), agenda); if (postUpdateEvent) { eventBus.post(new AgendaUpdateEvent(agenda, LocalDateTime.now())); } } /** {@inheritDoc} */ @Override public void deleteAgenda(AgendaId agendaId) { agendaDao.deleteAgenda(agendaId); agendaCache.evict(agendaId); } }