All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sportradar.unifiedodds.sdk.caching.markets.InvariantMarketDescriptionCache Maven / Gradle / Ivy
/*
* Copyright (C) Sportradar AG. See LICENSE for full license governing this code
*/
package com.sportradar.unifiedodds.sdk.caching.markets;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.sportradar.uf.sportsapi.datamodel.DescMarket;
import com.sportradar.uf.sportsapi.datamodel.MarketDescriptions;
import com.sportradar.unifiedodds.sdk.caching.ci.markets.MarketDescriptionCI;
import com.sportradar.unifiedodds.sdk.entities.markets.MarketDescription;
import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException;
import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException;
import com.sportradar.unifiedodds.sdk.exceptions.internal.IllegalCacheStateException;
import com.sportradar.unifiedodds.sdk.impl.DataProvider;
import com.sportradar.unifiedodds.sdk.impl.ObservableDataProvider;
import com.sportradar.unifiedodds.sdk.impl.SDKTaskScheduler;
import com.sportradar.unifiedodds.sdk.impl.markets.MappingValidatorFactory;
import com.sportradar.unifiedodds.sdk.impl.markets.MarketDescriptionImpl;
import com.sportradar.utils.SdkHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* Created on 14/06/2017.
* // TODO @eti: Javadoc
*/
public class InvariantMarketDescriptionCache implements MarketDescriptionCache {
private static final Logger logger = LoggerFactory.getLogger(InvariantMarketDescriptionCache.class);
private final Cache cache;
private final DataProvider dataProvider;
private final ObservableDataProvider additionalMappingsProvider;
private final MappingValidatorFactory mappingValidatorFactory;
private final List prefetchLocales;
private final List fetchedLocales;
private final ReentrantLock fetchLock = new ReentrantLock();
private boolean hasTimerElapsedOnce;
public InvariantMarketDescriptionCache(Cache cache,
DataProvider dataProvider,
ObservableDataProvider additionalMappingsProvider,
MappingValidatorFactory mappingValidatorFactory,
SDKTaskScheduler scheduler,
List prefetchLocales) {
Preconditions.checkNotNull(cache);
Preconditions.checkNotNull(dataProvider);
Preconditions.checkNotNull(additionalMappingsProvider);
Preconditions.checkNotNull(mappingValidatorFactory);
Preconditions.checkNotNull(scheduler);
Preconditions.checkNotNull(prefetchLocales);
this.cache = cache;
this.dataProvider = dataProvider;
this.additionalMappingsProvider = additionalMappingsProvider;
this.mappingValidatorFactory = mappingValidatorFactory;
this.prefetchLocales = prefetchLocales;
this.fetchedLocales = new ArrayList<>();
scheduler.scheduleAtFixedRate("InvariantMarketCacheRefreshTask", this::onTimerElapsed, 5, 60 * 60 * 6, TimeUnit.SECONDS);
additionalMappingsProvider.registerWatcher(this.getClass(), this::additionalMappingsChanged);
}
@Override
public MarketDescription getMarketDescriptor(int marketId, String variant, List locales) throws IllegalCacheStateException, CacheItemNotFoundException {
Preconditions.checkArgument(marketId > 0);
String processingCacheId = String.valueOf(marketId);
MarketDescriptionCI cachedItem = getMarketInternal(processingCacheId, locales);
return new MarketDescriptionImpl(cachedItem, locales);
}
@Override
public boolean loadMarketDescriptions() {
try{
fetchedLocales.clear();
logger.debug("Loading invariant market descriptions for [{}] (user request).",
prefetchLocales.stream().map(Locale::getLanguage).collect(Collectors.joining(",")));
fetchMissingData(prefetchLocales);
}
catch(Exception e){
logger.warn("An error occurred while fetching market description for languages [{}]",
prefetchLocales.stream().map(Locale::getLanguage).collect(Collectors.joining(",")), e);
return false;
}
return true;
}
public List getAllInvariantMarketDescriptions(List locales) throws IllegalCacheStateException, CacheItemNotFoundException {
Preconditions.checkNotNull(locales);
Preconditions.checkArgument(!locales.isEmpty());
// ensure all locales are present & fetch them if needed
MarketDescriptionCI cachedItem = getMarketInternal("1", locales);
return cache.asMap().values().stream()
.map(ci -> new MarketDescriptionImpl(ci, locales))
.collect(Collectors.toList());
}
private void onTimerElapsed() {
logger.info("Executing invariant market cache refresh");
List locales2fetch;
if (hasTimerElapsedOnce) {
locales2fetch = prefetchLocales;
} else {
locales2fetch = prefetchLocales.stream()
.filter(pLocale -> !fetchedLocales.contains(pLocale)).collect(Collectors.toList());
}
logger.debug("Loading invariant market descriptions for [{}] (timer).",
locales2fetch.stream().map(Locale::getLanguage).collect(Collectors.joining(",")));
fetchLock.lock();
try {
if (hasTimerElapsedOnce) {
fetchedLocales.clear();
cache.invalidateAll();
}
if (!locales2fetch.isEmpty()) {
fetchMissingData(locales2fetch);
}
} catch (Exception e) { // so the timer does not die
logger.warn("An error occurred while periodically fetching market description for languages [{}]",
locales2fetch.stream().map(Locale::getLanguage).collect(Collectors.joining(",")),
e);
} finally {
fetchLock.unlock();
}
hasTimerElapsedOnce = true;
}
private MarketDescriptionCI getMarketInternal(String id, List locales) throws IllegalCacheStateException, CacheItemNotFoundException {
Preconditions.checkNotNull(locales);
Preconditions.checkArgument(!locales.isEmpty());
MarketDescriptionCI description = cache.getIfPresent(id);
try {
fetchLock.lock();
if (description != null && getMissingLocales(description, locales).isEmpty()) {
return description;
}
} finally {
fetchLock.unlock();
}
try {
fetchLock.lock();
description = cache.getIfPresent(id);
List missingLocales = getMissingLocales(description, locales);
if (missingLocales.isEmpty()) {
return description;
}
// validate the missing global locales so the SDK doesn't request infinite api requests, if a producer starts sending "unknown" markets
List missingGlobalLocales = SdkHelper.findMissingLocales(fetchedLocales, locales);
if (!missingGlobalLocales.isEmpty()) {
fetchMissingData(getMissingLocales(description, locales));
}
} finally {
fetchLock.unlock();
}
description = cache.getIfPresent(id);
if (description == null || !getMissingLocales(description, locales).isEmpty()) {
throw new CacheItemNotFoundException("After successful market fetch, the cache item should be complete[" + id + "], but its missing");
}
return description;
}
private void fetchMissingData(List missingLocales) throws IllegalCacheStateException {
Preconditions.checkNotNull(missingLocales);
try {
for (Locale missingLocale : missingLocales) {
merge(missingLocale, dataProvider.getData(missingLocale));
}
initStaticMappingsEnrichment();
} catch (DataProviderException e) {
throw new IllegalCacheStateException("An error occurred while fetching invariant descriptors in [" + missingLocales + "]", e);
}
}
private void merge(Locale locale, MarketDescriptions data) {
Preconditions.checkNotNull(locale);
Preconditions.checkNotNull(data);
data.getMarket().forEach(market -> {
String processingCacheItemId = String.valueOf(market.getId());
MarketDescriptionCI cachedItem = cache.getIfPresent(processingCacheItemId);
if (cachedItem == null) {
cachedItem = new MarketDescriptionCI(market, mappingValidatorFactory, locale);
cache.put(processingCacheItemId, cachedItem);
} else {
cachedItem.merge(market, locale);
}
});
fetchedLocales.add(locale);
}
private void initStaticMappingsEnrichment() {
try {
MarketDescriptions data = additionalMappingsProvider.getData();
if (data == null || data.getMarket() == null) {
if (additionalMappingsProvider.logErrors()) {
logger.warn("Additional mappings provider returned null data");
}
return;
}
enrichStaticMappings(data.getMarket());
} catch (Exception e) {
if (additionalMappingsProvider.logErrors()) {
logger.warn("An exception occurred while enriching static mappings with additional mappings, exc:", e);
}
}
}
private void enrichStaticMappings(List markets) {
Preconditions.checkNotNull(markets);
markets.forEach(m -> {
String processingCacheItemId = String.valueOf(m.getId());
MarketDescriptionCI cachedItem = cache.getIfPresent(processingCacheItemId);
if (cachedItem == null) {
if (additionalMappingsProvider.logErrors()) {
logger.warn("Handling additional mappings for unknown market: {}", m.getId());
}
return;
}
if (m.getMappings() == null || m.getMappings().getMapping() == null || m.getMappings().getMapping().isEmpty()) {
if (additionalMappingsProvider.logErrors()) {
logger.warn("Handling empty/null additional mappings for market: {}", m.getId());
}
return;
}
cachedItem.mergeAdditionalMappings(m.getMappings().getMapping());
});
}
private void additionalMappingsChanged() {
logger.info("Additional mappings callback invoked - triggering cache refresh");
onTimerElapsed();
}
private List getMissingLocales(MarketDescriptionCI item, List requiredLocales) {
Preconditions.checkNotNull(requiredLocales);
Preconditions.checkArgument(!requiredLocales.isEmpty());
if (item == null) {
return requiredLocales;
}
return SdkHelper.findMissingLocales(item.getCachedLocales(), requiredLocales);
}
}