package gov.nysenate.openleg.service.calendar.data;
import com.google.common.collect.Range;
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.calendar.data.CalendarDao;
import gov.nysenate.openleg.model.cache.CacheEvictIdEvent;
import gov.nysenate.openleg.model.cache.ContentCache;
import gov.nysenate.openleg.model.calendar.*;
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.base.data.CachingService;
import gov.nysenate.openleg.service.calendar.event.CalendarUpdateEvent;
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.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class CachedCalendarDataService implements CalendarDataService, CachingService<CalendarId>
{
private static final Logger logger = LoggerFactory.getLogger(CachedCalendarDataService.class);
@Autowired private CacheManager cacheManager;
@Autowired private CalendarDao calendarDao;
@Autowired private EventBus eventBus;
@Value("${calendar.cache.size}") private long calendarCacheSizeMb;
private Cache calendarCache;
@PostConstruct
private void init() {
setupCaches();
eventBus.register(this);
}
@PreDestroy
private void cleanUp() {
evictCaches();
cacheManager.removeCache(ContentCache.CALENDAR.name());
}
/** --- CachingService implementation --- */
/** {@inheritDoc} */
@Override
public List<Ehcache> getCaches() {
return Arrays.asList(calendarCache);
}
/** {@inheritDoc} */
@Override
public void setupCaches() {
calendarCache = new Cache(new CacheConfiguration().name(ContentCache.CALENDAR.name())
.eternal(true)
.maxBytesLocalHeap(calendarCacheSizeMb, MemoryUnit.MEGABYTES)
.sizeOfPolicy(defaultSizeOfPolicy()));
cacheManager.addCache(calendarCache);
}
/** {@inheritDoc} */
@Override
public void evictCaches() {
logger.info("clearing calendar cache");
calendarCache.removeAll();
}
/** {@inheritDoc} */
@Override
public void evictContent(CalendarId calendarId) {
calendarCache.remove(calendarId);
}
/** {@inheritDoc} */
@Subscribe
@Override
public void handleCacheEvictEvent(CacheEvictEvent evictEvent) {
if (evictEvent.affects(ContentCache.CALENDAR)) {
evictCaches();
}
}
/** {@inheritDoc} */
@Subscribe
@Override
public void handleCacheEvictIdEvent(CacheEvictIdEvent<CalendarId> evictIdEvent) {
if (evictIdEvent.affects(ContentCache.CALENDAR)) {
evictContent(evictIdEvent.getContentId());
}
}
/** {@inheritDoc} */
@Override
public void warmCaches() {
evictCaches();
getCalendars(LocalDate.now().getYear(), SortOrder.ASC, LimitOffset.ALL);
}
/** {@inheritDoc} */
@Subscribe
@Override
public synchronized void handleCacheWarmEvent(CacheWarmEvent warmEvent) {
if (warmEvent.affects(ContentCache.CALENDAR)) {
warmCaches();
}
}
/** --- CalendarDataService implementation --- */
/** {@inheritDoc} */
@Override
public Calendar getCalendar(CalendarId calendarId) throws CalendarNotFoundEx {
if (calendarId == null) {
throw new IllegalArgumentException("CalendarId cannot be null.");
}
Element element = calendarCache.get(calendarId);
if (element != null) {
logger.debug("Calendar Cache HIT !! {}", calendarId);
return (Calendar) element.getObjectValue();
}
try {
Calendar calendar = calendarDao.getCalendar(calendarId);
calendarCache.put(new Element(calendarId, calendar));
return calendar;
}
catch (DataAccessException ex) {
logger.debug("Error retrieving calendar " + calendarId + ":\n" + ex.getMessage());
throw new CalendarNotFoundEx(calendarId, ex);
}
}
/** {@inheritDoc} */
@Override
public CalendarActiveList getActiveList(CalendarActiveListId activeListId) throws CalendarNotFoundEx {
if (activeListId == null) {
throw new IllegalArgumentException("active list id cannot be null");
}
CalendarActiveList activeList = getCalendar(activeListId).getActiveList(activeListId.getSequenceNo());
if (activeList != null) {
return activeList;
}
throw new CalendarNotFoundEx(activeListId);
}
/** {@inheritDoc} */
@Override
public CalendarSupplemental getCalendarSupplemental(CalendarSupplementalId supplementalId) throws CalendarNotFoundEx {
if (supplementalId == null) {
throw new IllegalArgumentException("active list id cannot be null");
}
CalendarSupplemental calSup = getCalendar(supplementalId).getSupplemental(supplementalId.getVersion());
if (calSup != null ) {
return calSup;
}
throw new CalendarNotFoundEx(supplementalId);
}
/** {@inheritDoc} */
@Override
public Optional<Range<Integer>> getCalendarYearRange() {
try {
return Optional.of(calendarDao.getActiveYearRange());
} catch (EmptyResultDataAccessException ex) {
return Optional.empty();
}
}
/** {@inheritDoc} */
@Override
public int getCalendarCount() {
return calendarDao.getCalendarCount();
}
/** {@inheritDoc} */
@Override
public int getCalendarCount(int year) {
try {
return calendarDao.getCalendarCount(year);
}
catch (DataAccessException ex) {
logger.warn("Error retrieving calendar id count for " + year + ":\n" + ex.getMessage());
return 0;
}
}
/** {@inheritDoc} */
@Override
public int getActiveListCount(int year) {
try {
return calendarDao.getActiveListCount(year);
}
catch (DataAccessException ex) {
logger.warn("Error retrieving active list id count for " + year + ":\n" + ex.getMessage());
return 0;
}
}
/** {@inheritDoc} */
@Override
public int getSupplementalCount(int year) {
try {
return calendarDao.getCalendarSupplementalCount(year);
}
catch (DataAccessException ex) {
logger.warn("Error retrieving floor calendar id count for " + year + ":\n" + ex.getMessage());
return 0;
}
}
/**
* {@inheritDoc}
*/
@Override
public List<Calendar> getCalendars(int year, SortOrder sortOrder, LimitOffset limitOffset) {
return calendarDao.getCalendarIds(year, sortOrder, limitOffset).stream()
.map(this::getCalendar)
.collect(Collectors.toList());
}
/** {@inheritDoc} */
@Override
public List<CalendarActiveList> getActiveLists(int year, SortOrder sortOrder, LimitOffset limitOffset) {
return calendarDao.getActiveListIds(year, sortOrder, limitOffset).stream()
.map(this::getActiveList)
.collect(Collectors.toList());
}
/** {@inheritDoc} */
@Override
public List<CalendarSupplemental> getCalendarSupplementals(int year, SortOrder sortOrder, LimitOffset limitOffset) {
return calendarDao.getCalendarSupplementalIds(year, sortOrder, limitOffset).stream()
.map(this::getCalendarSupplemental)
.collect(Collectors.toList());
}
/** {@inheritDoc} */
@Override
public void saveCalendar(Calendar calendar, SobiFragment sobiFragment, boolean postUpdateEvent) {
logger.debug("Persisting {}", calendar);
calendarDao.updateCalendar(calendar, sobiFragment);
calendarCache.put(new Element(calendar.getId(), calendar));
if (postUpdateEvent) {
eventBus.post(new CalendarUpdateEvent(calendar));
}
}
}