/** * */ package org.gcube.accounting.persistence; import java.io.File; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.TimeUnit; import org.gcube.accounting.aggregation.scheduler.AggregationScheduler; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.impl.ScopeBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Luca Frosini (ISTI - CNR) http://www.lucafrosini.com/ * */ public abstract class AccountingPersistenceBackendFactory { private static final Logger logger = LoggerFactory.getLogger(AccountingPersistenceBackendFactory.class); public final static String HOME_SYSTEM_PROPERTY = "user.home"; private static final String ACCOUTING_FALLBACK_FILENAME = "accountingFallback.log"; private static String fallbackLocation; private static Map accountingPersistenceBackends; private static Map fallbackLastCheck; public static final long FALLBACK_RETRY_TIME = 1000*60*10; // 10 min /** * @return the fallbackLastCheck */ protected static Long getFallbackLastCheck(String scope) { return fallbackLastCheck.get(scope); } static { accountingPersistenceBackends = new HashMap(); fallbackLastCheck = new HashMap(); } private static File file(File file) throws IllegalArgumentException { if(!file.isDirectory()){ file = file.getParentFile(); } // Create folder structure if not exist if (!file.exists()) { file.mkdirs(); } return file; } protected synchronized static void setFallbackLocation(String path){ if(fallbackLocation == null){ if(path==null){ path = System.getProperty(HOME_SYSTEM_PROPERTY); } file(new File(path)); fallbackLocation = path; } } protected static FallbackPersistenceBackend createFallback(String scope){ logger.debug("Creating {} for scope {}", FallbackPersistenceBackend.class.getSimpleName(), scope); File fallbackFile = null; if(scope!=null){ ScopeBean bean = new ScopeBean(scope); /* if(bean.is(Type.VRE)){ bean = bean.enclosingScope(); } */ String name = bean.name(); fallbackFile = new File(fallbackLocation, String.format("%s.%s", name, ACCOUTING_FALLBACK_FILENAME)); }else{ fallbackFile = new File(fallbackLocation, ACCOUTING_FALLBACK_FILENAME); } FallbackPersistenceBackend fallbackPersistence = new FallbackPersistenceBackend(fallbackFile); fallbackPersistence.setAggregationScheduler(AggregationScheduler.newInstance()); return fallbackPersistence; } protected static AccountingPersistenceBackend discoverAccountingPersistenceBackend(String scope){ logger.debug("Discovering {} for scope {}", AccountingPersistenceBackend.class.getSimpleName(), scope); ServiceLoader serviceLoader = ServiceLoader.load(AccountingPersistenceBackend.class); for (AccountingPersistenceBackend foundPersistence : serviceLoader) { try { String foundPersistenceClassName = foundPersistence.getClass().getSimpleName(); logger.debug("Testing {}", foundPersistenceClassName); AccountingPersistenceConfiguration configuration = new AccountingPersistenceConfiguration(foundPersistenceClassName); foundPersistence.prepareConnection(configuration); /* * Uncomment the following line of code if you want to try * to create a test UsageRecord before setting the * foundPersistence as default * * foundPersistence.accountWithFallback(TestUsageRecord.createTestServiceUsageRecord()); */ logger.debug("{} will be used.", foundPersistenceClassName); foundPersistence.setAggregationScheduler(AggregationScheduler.newInstance()); foundPersistence.setFallback(createFallback(scope)); return foundPersistence; } catch (Exception e) { logger.error(String.format("%s not initialized correctly. It will not be used. Trying the next one if any.", foundPersistence.getClass().getSimpleName()), e); } } return null; }; protected static AccountingPersistenceBackend rediscoverAccountingPersistenceBackend(AccountingPersistenceBackend actual, String scope){ Long now = Calendar.getInstance().getTimeInMillis(); Long lastCheckTimestamp = fallbackLastCheck.get(scope); logger.debug("Last check for scope {} was {}", scope, lastCheckTimestamp); boolean myTurn = false; synchronized (accountingPersistenceBackends) { if( (lastCheckTimestamp + FALLBACK_RETRY_TIME) <= now ){ logger.debug("The {} for scope {} is {}. Is time to rediscover if there is another possibility.", AccountingPersistenceBackend.class.getSimpleName(), scope, actual.getClass().getSimpleName()); logger.trace("Renewing Last check Timestamp. The next one will be {}", now); fallbackLastCheck.put(scope, now); myTurn=true; logger.debug("I win. It is my turn to rediscover {} in scope {}", AccountingPersistenceBackend.class.getSimpleName(), scope); } } if(myTurn){ AccountingPersistenceBackend discoveredPersistenceBackend = discoverAccountingPersistenceBackend(scope); synchronized (accountingPersistenceBackends) { if(discoveredPersistenceBackend!=null){ /* * Passing the aggregator to the new AccountingPersistenceBackend * so that the buffered records will be persisted with the * new method * */ discoveredPersistenceBackend.setAggregationScheduler(actual.getAggregationScheduler()); // Removing timestamp which is no more needed fallbackLastCheck.remove(scope); accountingPersistenceBackends.put(scope, discoveredPersistenceBackend); /* * Not needed because close has no effect. Removed to * prevent problem in cases of future changes. * try { * actual.close(); * } catch (Exception e) { * logger.error("Error closing {} for scope {} which has been substituted with {}.", * actual.getClass().getSimpleName(), scope, * discoveredPersistenceBackend.getClass().getSimpleName(), e); * } * */ return discoveredPersistenceBackend; } } } long nextCheck = (lastCheckTimestamp + FALLBACK_RETRY_TIME) - Calendar.getInstance().getTimeInMillis(); float nextCheckInSec = nextCheck/1000; logger.debug("The {} for scope {} is going to be used is {}. Next retry in {} msec (about {} sec)", AccountingPersistenceBackend.class.getSimpleName(), scope, actual.getClass().getSimpleName(), nextCheck, nextCheckInSec); return actual; } protected static AccountingPersistenceBackend getPersistenceBackend() { String scope = ScopeProvider.instance.get(); if(scope==null){ logger.error("No Scope available. FallbackPersistence will be used"); return createFallback(null); } AccountingPersistenceBackend persistence = null; logger.debug("Going to synchronized block in getPersistenceBackend"); synchronized (accountingPersistenceBackends) { persistence = accountingPersistenceBackends.get(scope); logger.debug("{} {}", AccountingPersistenceBackend.class.getSimpleName(), persistence); if(persistence==null){ /* * Setting FallbackPersistence and unlocking. * This is used to avoid deadlock on IS node which try to use * itself to query configuration. */ persistence = createFallback(scope); accountingPersistenceBackends.put(scope, persistence); long now = Calendar.getInstance().getTimeInMillis(); /* The AccountingPersistenceBackend is still to be discovered * setting the last check advanced in time to force rediscover. */ fallbackLastCheck.put(scope, ((now - FALLBACK_RETRY_TIME) - 1)); } } if(persistence instanceof FallbackPersistenceBackend){ persistence = rediscoverAccountingPersistenceBackend(persistence, scope); } return persistence; } /** * @param timeout * @param timeUnit * @throws Exception */ public static void flushAll(long timeout, TimeUnit timeUnit) { for(String scope : accountingPersistenceBackends.keySet()){ AccountingPersistenceBackend apb = accountingPersistenceBackends.get(scope); try { logger.debug("Flushing records in scope {}", scope); apb.flush(timeout, timeUnit); }catch(Exception e){ logger.error("Unable to flush records in scope {} with {}", scope, apb); } } } }