Java tutorial
/* * Copyright (c) 2013 The Finnish National Board of Education - Opetushallitus * * This program is free software: Licensed under the EUPL, Version 1.1 or - as * soon as they will be approved by the European Commission - subsequent versions * of the EUPL (the "Licence"); * * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: http://www.osor.eu/eupl/ * * This program 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 * European Union Public Licence for more details. */ package fi.vm.sade.osoitepalvelu.kooste.scheduled; import fi.vm.sade.osoitepalvelu.kooste.common.route.CamelRequestContext; import fi.vm.sade.osoitepalvelu.kooste.common.route.DefaultCamelRequestContext; import fi.vm.sade.osoitepalvelu.kooste.common.route.cas.CasDisabledCasTicketProvider; import fi.vm.sade.osoitepalvelu.kooste.dao.organisaatio.OrganisaatioRepository; import fi.vm.sade.osoitepalvelu.kooste.service.AbstractService; import fi.vm.sade.osoitepalvelu.kooste.service.organisaatio.OrganisaatioService; import fi.vm.sade.osoitepalvelu.kooste.route.OrganisaatioServiceRoute; import fi.vm.sade.osoitepalvelu.kooste.route.dto.OrganisaatioDetailsDto; import org.apache.camel.RuntimeCamelException; import org.joda.time.DateTime; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.Callable; /** * Keep organisaatios in cache. */ @Service public class ScheduledOrganisaatioCacheTask extends AbstractService { // While the timeout for Camel requests is 15s with 2 retries, this should allow // Organisaatio service to have over 2 minutes of downtime or couple of 500 errors at any given point // (while the overall process would take about half an hour): public static final int MAX_TRIES = 3; public static final int WAIT_BEFORE_RETRY_MILLIS = 3000; private static final int LOGGIN_INTERVAL = 1000; @Autowired private OrganisaatioService organisaatioService; @Autowired private OrganisaatioServiceRoute organisaatioServiceRoute; @Autowired private OrganisaatioRepository organisaatioRepository; @Value("${web.url.cas}") protected String casService; @Value("${organisaatio.cache.valid.from:}") private String cacheValidFrom; // Every working day night at 3 AM @Scheduled(cron = "0 0 3 * * MON-FRI") public void refreshOrganisaatioCache() { logger.info("BEGIN SCHEDULED refreshOrganisaatioCache."); showCacheState(); // No CAS here (not needed for reading organisaatio service): final CamelRequestContext context = new DefaultCamelRequestContext( new ProviderOverriddenCasTicketCache(new CasDisabledCasTicketProvider())); List<String> oids = retryOnCamelError(new Callable<List<String>>() { public List<String> call() { return organisaatioServiceRoute.findAllOrganisaatioOids(context); } }, MAX_TRIES, WAIT_BEFORE_RETRY_MILLIS); removeCachedDeletedOrganisaatios(oids); logger.info("Found {} organisaatios to refresh.", oids.size()); try { int i = 0; for (final String oid : oids) { ++i; // This will purge the possibly cached organisaatio from both method level memory based EH cache and // and the underlying MongoDB cache: organisaatioService.purgeOrganisaatioByOidCache(oid); // ...and renew the cache: retryOnCamelError(new Callable<OrganisaatioDetailsDto>() { public OrganisaatioDetailsDto call() { return organisaatioService.getdOrganisaatioByOid(oid, context); } }, MAX_TRIES, WAIT_BEFORE_RETRY_MILLIS); logger.debug("Updated organisaatio {} (Total: {} / {})", new Object[] { oid, i, oids.size() }); } } finally { logger.debug("UPDATING y-tunnus details..."); organisaatioService.updateOrganisaatioYtunnusDetails(context); logger.debug("DONE UPDATING y-tunnus details"); } logger.info("END SCHEDULED refreshOrganisaatioCache."); } private void removeCachedDeletedOrganisaatios(List<String> oids) { logger.info("REMOVING obsolete organisaatios from cache."); List<String> cachedObsoleteOids = organisaatioService.findAllOidsOfCachedOrganisaatios(); cachedObsoleteOids.removeAll(oids); for (String oid : cachedObsoleteOids) { organisaatioService.purgeOrganisaatioByOidCache(oid); } logger.info("REMOVED {} obsolete organsiaatios from cache.", cachedObsoleteOids.size()); } /** * Does not purge fresh cache records but ensures that all organisaatios are cached. */ public void ensureOrganisaatioCacheFresh() { logger.info("BEGIN SCHEDULED ensureOrganisaatioCacheFresh."); showCacheState(); LocalDate cacheInvalidBefore = null; if (cacheValidFrom != null && cacheValidFrom.length() > 0) { cacheInvalidBefore = LocalDate.parse(cacheValidFrom); } // No CAS here (not needed for reading organisaatio service): final DefaultCamelRequestContext context = new DefaultCamelRequestContext( new ProviderOverriddenCasTicketCache(new CasDisabledCasTicketProvider())); if (cacheInvalidBefore != null && new DateTime().compareTo(cacheInvalidBefore.toDateTimeAtStartOfDay()) < 0) { context.setOverriddenTime(cacheInvalidBefore.toDateTimeAtStartOfDay()); } List<String> oids = retryOnCamelError(new Callable<List<String>>() { public List<String> call() { return organisaatioServiceRoute.findAllOrganisaatioOids(context); } }, MAX_TRIES, WAIT_BEFORE_RETRY_MILLIS); removeCachedDeletedOrganisaatios(oids); logger.info("Found {} organisaatios to ensure cache.", oids.size()); boolean infoUpdated = false; try { int i = 0; for (final String oid : oids) { ++i; // ...and renew the cache: long rc = context.getRequestCount(); retryOnCamelError(new Callable<OrganisaatioDetailsDto>() { public OrganisaatioDetailsDto call() { return organisaatioService.getdOrganisaatioByOid(oid, context); } }, MAX_TRIES, WAIT_BEFORE_RETRY_MILLIS); if (rc != context.getRequestCount()) { infoUpdated = true; logger.debug("Updated organisaatio {} (Total: {} / {})", new Object[] { oid, i, oids.size() }); } else if (i % LOGGIN_INTERVAL == 0) { logger.info("Organisaatio ensure data fresh task status: {} / {}", new Object[] { i, oids.size() }); } } } finally { if (infoUpdated) { logger.debug("UPDATING y-tunnus details..."); organisaatioService.updateOrganisaatioYtunnusDetails(context); logger.debug("DONE UPDATING y-tunnus details"); } } logger.info("END SCHEDULED ensureOrganisaatioCacheFresh."); } private void showCacheState() { DateTime oldestEntry = organisaatioRepository.findOldestCachedEntry(); if (oldestEntry != null) { logger.info("Oldest cache entry: {}", oldestEntry); } else { logger.info("No cache entries found."); } } /** * Retries implemented here, not in the actual Camel routes, because we don't want them to be part of every Camel * call (calls made by the user). In this scheduled task, however, it is more important to continue the process * after possible downtime. * * @param task to call * @param maxRetries max retries * @param retryWaitTimeMillis millis to wait before retryOnCamelError * @param <T> type to return * @return the result of the successful call */ protected <T> T retryOnCamelError(Callable<T> task, int maxRetries, long retryWaitTimeMillis) { RuntimeCamelException originalError = null; for (int j = 0; j < maxRetries + 1; ++j) { try { return task.call(); } catch (Exception e) { if (!(e instanceof RuntimeCamelException)) { // For every other error, fail: throw new IllegalStateException("Scheduled task error:" + e.getMessage(), e); } if (originalError == null) { originalError = (RuntimeCamelException) e; } logger.warn("Error fetching data: " + e.getMessage(), e); try { Thread.sleep(retryWaitTimeMillis); } catch (InterruptedException er) { logger.warn("Error while sleeping: " + er.getMessage(), er); } if (j < maxRetries) { logger.info("Retrying..."); } } } logger.error("SCHEDULED task exhausted after " + maxRetries + " retries. Original exception: " + originalError.getMessage(), originalError); throw originalError; } }