All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sportradar.unifiedodds.sdk.caching.impl.SportEventCacheImpl 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.Equivalence;
import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.inject.Inject;
import com.sportradar.uf.sportsapi.datamodel.*;
import com.sportradar.unifiedodds.sdk.BookingManager;
import com.sportradar.unifiedodds.sdk.SDKInternalConfiguration;
import com.sportradar.unifiedodds.sdk.caching.*;
import com.sportradar.unifiedodds.sdk.caching.impl.ci.CacheItemFactory;
import com.sportradar.unifiedodds.sdk.entities.*;
import com.sportradar.unifiedodds.sdk.exceptions.internal.CacheItemNotFoundException;
import com.sportradar.unifiedodds.sdk.exceptions.internal.CommunicationException;
import com.sportradar.unifiedodds.sdk.exceptions.internal.IllegalCacheStateException;
import com.sportradar.unifiedodds.sdk.impl.MappingTypeProvider;
import com.sportradar.utils.URN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ExecutionException;

/**
 * Implements methods used to access sport event data
 */
public class SportEventCacheImpl implements SportEventCache, DataRouterListener {
    /**
     * The {@link Logger} instance used to log {@link SportEventCacheImpl} events
     */
    private static final Logger logger = LoggerFactory.getLogger(SportEventCacheImpl.class);

    /**
     * A {@link Cache} instance used to cache sport events data
     */
    private final Cache sportEventsCache;

    /**
     * A factory used to build specific sport event cache items
     */
    private final CacheItemFactory cacheItemFactory;

    /**
     * The {@link MappingTypeProvider} instance used to detect different CI types
     */
    private final MappingTypeProvider mappingTypeProvider;

    /**
     * The {@link DataRouterManager} instance used to initiate data requests
     */
    private final DataRouterManager dataRouterManager;

    /**
     * The default {@link Locale}
     */
    private final Locale defaultLocale;

    @Inject
    SportEventCacheImpl(CacheItemFactory cacheItemFactory,
                        MappingTypeProvider mappingTypeProvider,
                        DataRouterManager dataRouterManager,
                        SDKInternalConfiguration sdkInternalConfiguration,
                        Cache sportEventsCache) {
        Preconditions.checkNotNull(cacheItemFactory);
        Preconditions.checkNotNull(mappingTypeProvider);
        Preconditions.checkNotNull(dataRouterManager);
        Preconditions.checkNotNull(sdkInternalConfiguration);
        Preconditions.checkNotNull(sportEventsCache);

        this.cacheItemFactory = cacheItemFactory;
        this.mappingTypeProvider = mappingTypeProvider;
        this.dataRouterManager = dataRouterManager;
        this.defaultLocale = sdkInternalConfiguration.getDefaultLocale();
        this.sportEventsCache = sportEventsCache;
    }

    /**
     * Returns a {@link SportEventCI} instance representing a cached sport event data
     *
     * @param id an {@link URN} specifying the id of the sport event
     * @return a {@link SportEventCI} instance representing cached sport event data
     */
    @Override
    public SportEventCI getEventCacheItem(URN id) throws CacheItemNotFoundException {
        Preconditions.checkNotNull(id);

        try {
            return sportEventsCache.get(id, () -> {
                logger.info("Cache miss for[{}], providing CI", id);
                try {
                    return provideEventCI(id);
                } catch (IllegalCacheStateException e) {
                    throw new CacheItemNotFoundException(String.format("An error occurred while loading a new cache item '%s', ex: ", id), e);
                }
            });
        } catch (ExecutionException e) {
            throw new CacheItemNotFoundException(String.format("Cache item could not be loaded[%s], ex: ", id), e);
        }
    }

    /**
     * Returns a {@link List} containing id's of sport events, which belong to a specific tournament
     *
     * @param tournamentId an {@link URN} specifying the id of the tournament to which the events should relate
     * @return a {@link List} containing id's of sport events, which belong to the specified tournament
     */
    @Override
    public List getEventIds(URN tournamentId) throws IllegalCacheStateException {
        logger.debug("Providing tournament[{}] event IDs", tournamentId);
        try {
            return dataRouterManager.requestEventsFor(defaultLocale, tournamentId);
        } catch (CommunicationException e) {
            throw new IllegalCacheStateException("Error occurred while fetching tournament schedule[" + tournamentId + "]", e);
        }
    }

    /**
     * Returns a {@link List} containing id's of sport events, which are scheduled for a specific date - if provided;
     * otherwise a {@link List} of currently live events is returned
     *
     * @param date an optional {@link Date} for which the data is provided
     * @return a {@link List} of events that are happening on the specified {@link Date};
     * or a {@link List} of currently live events
     */
    @Override
    public List getEventIds(Date date) throws IllegalCacheStateException {
        logger.debug("Providing event IDs for {}", date == null ? "live" : date);
        try {
            return dataRouterManager.requestEventsFor(defaultLocale, date);
        } catch (CommunicationException e) {
            throw new IllegalCacheStateException("Error occurred while fetching date schedule for " + (date == null ? "live" : date), e);
        }
    }

    /**
     * Purges an item from the {@link SportEventCache}
     *
     * @param id The {@link URN} specifying the event which should be purged
     */
    @Override
    public void purgeCacheItem(URN id) {
        if (id == null) {
            return;
        }

        logger.debug("Purging CI[{}]", id);
        sportEventsCache.invalidate(id);
    }

    @Override
    public void onSportEventFetched(URN id, SAPISportEvent data, Locale dataLocale) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(id);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onSportEventFetched -> Failed to provide valid mapping type for id [{}]", id);
                return;
            }

            if (mappingType.equals(Match.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildMatchCI(id, data, dataLocale));
            } else if (mappingType.equals(Stage.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, dataLocale));
            }
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onChildSportEventFetched(URN id, SAPISportEventChildren.SAPISportEvent data, Locale dataLocale) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(id);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onChildSportEventFetched -> Failed to provide valid mapping type for id [{}]", id);
                return;
            }

            if (mappingType.equals(Match.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildMatchCI(id, data, dataLocale));
            } else if (mappingType.equals(Stage.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, dataLocale));
            }
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onTournamentExtendedFetched(URN id, SAPITournamentExtended data, Locale dataLocale) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(id);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onTournamentExtendedFetched -> Failed to provide valid mapping type for id [{}]", id);
                return;
            }

            if (isTournamentCIType(mappingType)) {
                sportEventsCache.put(id, cacheItemFactory.buildTournamentCI(id, data, dataLocale));
            } else if (mappingType.equals(Stage.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, dataLocale));
            } else {
                logger.warn("SportEventCache.onTournamentExtendedFetched -> discarding data, mapping type not supported. id:{}, type:{}", id, mappingType);
            }
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onTournamentInfoEndpointFetched(URN requestedId, URN tournamentId, URN seasonId, SAPITournamentInfoEndpoint data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(tournamentId);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        if (requestedId.equals(tournamentId)) {
            // if the requested id is the same as the tournament id, we can store as the "full current season response" and "full latest tournament response"
            storeTournamentInfoEndpoint(tournamentId, data, dataLocale, requester);

            if (seasonId != null) {
                storeTournamentInfoEndpoint(seasonId, data, dataLocale, requester);
            }
        } else {
            // if we didn't request the "outer" tournament, we can not store it as such, because the data might be different - ex: previous or next season request
            storeTournamentInfoEndpoint(seasonId, data, dataLocale, requester);
        }
    }

    @Override
    public void onStageSummaryEndpointFetched(URN id, SAPIStageSummaryEndpoint data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onMatchSummaryEndpointFetched(URN id, SAPIMatchSummaryEndpoint data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildMatchCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onFixtureFetched(URN id, SAPIFixture data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(id);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onFixtureFetched -> Failed to provide valid mapping type for id [{}]", id);
                return;
            }

            if (mappingType.equals(Match.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildMatchCI(id, data, dataLocale));
            } else if (mappingType.equals(Stage.class)) {
                sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, dataLocale));
            } else {
                logger.warn("SportEventCache.onFixtureFetched -> discarding data, mapping type not supported. id:{}, type:{}", id, mappingType);
            }
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onTournamentFetched(URN id, SAPITournament data, Locale locale) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(locale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(id);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onTournamentFetched -> Failed to provide valid mapping type for id [{}]", id);
                return;
            }

            if (isTournamentCIType(mappingType)) {
                sportEventsCache.put(id, cacheItemFactory.buildTournamentCI(id, data, locale));
            } else if (mappingType == Stage.class) {
                sportEventsCache.put(id, cacheItemFactory.buildStageCI(id, data, locale));
            } else {
                logger.warn("SportEventCache.onTournamentFetched -> discarding data, mapping type not supported. id:{}, type:{}", id, mappingType);
            }
        } else {
            ifPresent.merge(data, locale);
        }
    }

    @Override
    public void onMatchTimelineFetched(URN id, SAPIMatchTimelineEndpoint data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        // we only merge such data, this request wont be triggered before the cache item is created anyway
        if (ifPresent != null) {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    public void onLotteryFetched(URN id, SAPILottery data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildLotteryCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    @SuppressWarnings("Duplicates") // its not a duplicate, different CI factory method
    public void onDrawFetched(URN id, SAPIDrawEvent data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildDrawCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    @SuppressWarnings("Duplicates") // its not a duplicate, different CI factory method
    public void onDrawFixtureFetched(URN id, SAPIDrawFixture data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildDrawCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    @Override
    @SuppressWarnings("Duplicates") // its not a duplicate, different CI factory method
    public void onDrawSummaryEndpointFetched(URN id, SAPIDrawSummary data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(id);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            sportEventsCache.put(id, cacheItemFactory.buildDrawCI(id, data, dataLocale));
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    /**
     * Method that gets triggered when the associated event gets booked trough the {@link BookingManager}
     *
     * @param id the {@link URN} of the event that was successfully booked
     */
    @Override
    public void onEventBooked(URN id) {
        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent instanceof CompetitionCI) {
            ((CompetitionCI) ifPresent).onEventBooked();
        } else {
            logger.warn("Received onEventBooked event for an unsupported event type, id: {}", id);
        }
    }

    /**
     * Adds fixture timestamp to cache so that the next fixture calls for the event goes through non-cached fixture provider
     *
     * @param id the {@link URN} of the event
     */
    @Override
    public void addFixtureTimestamp(URN id) {
        Cache cache = cacheItemFactory.getFixtureTimestampCache();
        cache.put(id, new Date());
    }

    private SportEventCI provideEventCI(URN id) throws CacheItemNotFoundException, IllegalCacheStateException {
        Preconditions.checkNotNull(id);

        Class mappedClazz = provideMappingType(id);
        if (isTournamentCIType(mappedClazz)) {
            return cacheItemFactory.buildTournamentCI(id);
        } else if (mappedClazz == Match.class) {
            return cacheItemFactory.buildMatchCI(id);
        } else if (mappedClazz == Stage.class) {
            return provideStageDerivedCI(id);
        } else if (mappedClazz == Lottery.class) {
            return cacheItemFactory.buildLotteryCI(id);
        } else if (mappedClazz == Draw.class) {
            return cacheItemFactory.buildDrawCI(id);
        }

        throw new CacheItemNotFoundException(String.format("Unsupported caching URN identifier[%s] with clazz[%s]", id, mappedClazz.getName()));
    }

    private boolean isTournamentCIType(Class clazz) {
        Preconditions.checkNotNull(clazz);

        return clazz == Tournament.class || clazz == BasicTournament.class || clazz == Season.class;
    }

    private SportEventCI provideStageDerivedCI(URN id) throws CacheItemNotFoundException, IllegalCacheStateException {
        Preconditions.checkNotNull(id);

        logger.debug("Pre-fetching summary endpoint(stage type detection)[{}]", id);
        try {
            dataRouterManager.requestSummaryEndpoint(defaultLocale, id, null);
        } catch (CommunicationException e) {
            throw new IllegalCacheStateException("An error occurred while performing StageCI summary request[" + id + "]", e);
        }

        SportEventCI ifPresent = sportEventsCache.getIfPresent(id);
        if (ifPresent != null) {
            return ifPresent;
        }

        throw new CacheItemNotFoundException("StageCI[" + id + "] data could not be found");
    }

    private void storeTournamentInfoEndpoint(URN tournamentId, SAPITournamentInfoEndpoint data, Locale dataLocale, CacheItem requester) {
        Preconditions.checkNotNull(tournamentId);
        Preconditions.checkNotNull(data);
        Preconditions.checkNotNull(dataLocale);

        SportEventCI ifPresent = sportEventsCache.getIfPresent(tournamentId);

        if (requester != null && !Equivalence.identity().equivalent(ifPresent, requester)) {
            requester.merge(data, dataLocale);
        }

        if (ifPresent == null) {
            Class mappingType;
            try {
                mappingType = provideMappingType(tournamentId);
            } catch (IllegalCacheStateException e) {
                logger.warn("SportEventCache.onTournamentInfoEndpointFetched -> Failed to provide valid mapping type for id [{}]", tournamentId);
                return;
            }

            if (isTournamentCIType(mappingType)) {
                sportEventsCache.put(tournamentId, cacheItemFactory.buildTournamentCI(tournamentId, data, dataLocale));
            } else if (mappingType.equals(Stage.class)) {
                sportEventsCache.put(tournamentId, cacheItemFactory.buildStageCI(tournamentId, data, dataLocale));
            } else {
                logger.warn("SportEventCache.onTournamentInfoEndpointFetched -> discarding data, mapping type not supported. id:{}, type:{}", tournamentId, mappingType);
            }
        } else {
            ifPresent.merge(data, dataLocale);
        }
    }

    private Class provideMappingType(URN id) throws IllegalCacheStateException {
        Preconditions.checkNotNull(id);

        return mappingTypeProvider.getMappingType(id)
                .orElseThrow(() -> new IllegalCacheStateException(String.format("Error providing mapping type for [%s]", id)));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy