com.sportradar.unifiedodds.sdk.caching.impl.LocalizedNamedValueCacheImpl Maven / Gradle / Ivy
/*
* Copyright (C) Sportradar AG. See LICENSE for full license governing this code
*/
package com.sportradar.unifiedodds.sdk.caching.impl;
import com.google.common.base.Preconditions;
import com.sportradar.unifiedodds.sdk.caching.LocalizedNamedValueCache;
import com.sportradar.unifiedodds.sdk.caching.ci.NamedValueCI;
import com.sportradar.unifiedodds.sdk.entities.LocalizedNamedValue;
import com.sportradar.unifiedodds.sdk.exceptions.internal.DataProviderException;
import com.sportradar.unifiedodds.sdk.impl.DataProvider;
import com.sportradar.unifiedodds.sdk.impl.SDKTaskScheduler;
import com.sportradar.unifiedodds.sdk.impl.entities.LocalizedNamedValueImpl;
import com.sportradar.utils.SdkHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* An implementation of {@link LocalizedNamedValueCache} used to cache {@link LocalizedNamedValue} items
*/
public class LocalizedNamedValueCacheImpl implements LocalizedNamedValueCache {
/**
* A {@link Logger} instance used to log {@link LocalizedNamedValueCache} entries
*/
private static final Logger cacheLog = LoggerFactory.getLogger(LocalizedNamedValueCacheImpl.class);
/**
* A {@link DataProvider} which is used to get new data
*/
private final DataProvider dataProvider;
/**
* A {@link List} of all supported {@link Locale}
*/
private final List defaultLocales;
/**
* A {@link ConcurrentHashMap} storing the translated values
*/
private final ConcurrentHashMap> namedValues;
/**
* A {@link List } of {@link Locale} that are already fetched
*/
private final List fetchedLocales;
/**
* The {@link Object} used to synchronize the access
*/
private final Object lock = new Object();
/**
* Initializes a new instance of {@link LocalizedNamedValueCacheImpl}
*
* @param dataProvider - a {@link DataProvider} that will be used to get new data
* @param scheduler - the {@link SDKTaskScheduler} used to perform repeating cache tasks
* @param defaultLocales - a {@link List} of all supported {@link Locale}
*/
public LocalizedNamedValueCacheImpl(DataProvider dataProvider, SDKTaskScheduler scheduler, List defaultLocales) {
Preconditions.checkNotNull(dataProvider);
Preconditions.checkNotNull(defaultLocales);
this.dataProvider = dataProvider;
this.defaultLocales = defaultLocales;
this.namedValues = new ConcurrentHashMap<>();
this.fetchedLocales = Collections.synchronizedList(new ArrayList<>());
scheduler.scheduleAtFixedRate("LocalizedNamedValueRefreshTask", this::onTimerElapsed, 24, 24, TimeUnit.HOURS);
}
/**
* Gets a {@link LocalizedNamedValue} with the specified translations
*
* @param id - the identifier of the localized value
* @param locales - a {@link List} of {@link Locale} in which the data is required
* @return - a {@link LocalizedNamedValue} with the specified translations
*/
@Override
public LocalizedNamedValue get(int id, List locales) {
Preconditions.checkArgument(id >= 0);
if (locales == null || locales.size() == 0) {
locales = defaultLocales;
}
ConcurrentHashMap cachedTranslations;
synchronized (lock) {
List missingLocales = SdkHelper.findMissingLocales(fetchedLocales, locales);
if (!missingLocales.isEmpty()) {
getInternal(missingLocales);
}
cachedTranslations = namedValues.get(id);
}
if (cachedTranslations == null) {
return new LocalizedNamedValueImpl(id, null, null);
}
return new LocalizedNamedValueImpl(id, cachedTranslations, locales.stream().findFirst().orElseGet(null));
}
/**
* Determines if the specified identifier exists in the current instance
*
* @param id - the identifier to check
* @return - true
if the value exists; otherwise false
*/
@Override
public boolean isValueDefined(int id) {
boolean exists;
synchronized (lock) {
if (fetchedLocales.isEmpty()) {
getInternal(Collections.singletonList(defaultLocales.stream().findFirst().orElse(Locale.ENGLISH)));
}
exists = namedValues.containsKey(id);
}
return exists;
}
/**
* Performs several calls of the {@link this#fetchAndMerge(Locale)}
*
* @param locales - a {@link List} of {@link Locale} in which the data should be retrieved
*/
private void getInternal(List locales) {
try {
locales.forEach(this::fetchAndMerge);
} catch (Exception ex) {
cacheLog.warn("An exception occurred while attempting to retrieve named values. [{}] Exception:", dataProvider, ex);
}
}
/**
* Fetches localized values using the provided {@link DataProvider}, the fetched data is
* than merged in the local cache
*
* @param locale - a {@link Locale} specifying the language in which the data should be fetched
*/
private void fetchAndMerge(Locale locale) {
Preconditions.checkNotNull(locale);
Object fetch;
try {
fetch = dataProvider.getData(locale);
} catch (DataProviderException e) {
cacheLog.warn("Error fetching Localized named values [{}] Exception:", dataProvider, e);
return;
}
List namedValueCIS = NamedValueCI.mapToNamedValuesCI(fetch);
namedValueCIS.forEach(fetchedVal -> {
ConcurrentHashMap storedData =
namedValues.computeIfAbsent(fetchedVal.getId(), k -> new ConcurrentHashMap<>());
storedData.put(locale, fetchedVal.getDescription());
});
fetchedLocales.add(locale);
cacheLog.info("{} {} retrieved for locale {}", namedValueCIS.size(), fetch.getClass().getName(), locale);
}
/**
* Timer scheduled for every 24h to refresh named values
*/
private synchronized void onTimerElapsed() {
try {
fetchedLocales.clear();
namedValues.clear();
defaultLocales.forEach(this::fetchAndMerge);
} catch (Exception ex) {
cacheLog.warn("An exception occurred while attempting to retrieve localized named values with the scheduled timer. [{}] Exception:", dataProvider, ex);
}
}
}