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.impl.OddsFeedSessionImpl Maven / Gradle / Ivy
/*
* Copyright (C) Sportradar AG. See LICENSE for full license governing this code
*/
package com.sportradar.unifiedodds.sdk.impl;
import com.google.common.base.Stopwatch;
import com.google.common.cache.Cache;
import com.google.inject.name.Named;
import com.sportradar.uf.datamodel.*;
import com.sportradar.unifiedodds.sdk.*;
import com.sportradar.unifiedodds.sdk.entities.ResourceTypeGroup;
import com.sportradar.unifiedodds.sdk.entities.SportEvent;
import com.sportradar.unifiedodds.sdk.exceptions.internal.ObjectNotFoundException;
import com.sportradar.unifiedodds.sdk.impl.oddsentities.MessageTimestampImpl;
import com.sportradar.unifiedodds.sdk.impl.processing.pipeline.CompositeMessageProcessor;
import com.sportradar.unifiedodds.sdk.oddsentities.*;
import com.sportradar.utils.URN;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class OddsFeedSessionImpl implements OddsFeedSession, MessageConsumer, FeedMessageProcessor {
private static final Logger logger = LoggerFactory.getLogger(OddsFeedSessionImpl.class);
private static final Logger clientInteractionLog = LoggerFactory.getLogger(LoggerDefinitions.UFSdkClientInteractionLog.class);
private final SDKInternalConfiguration config;
private final ProducerManager producerManager;
private final SportsInfoManager sportsInfoManager;
private final MessageReceiver messageReceiver;
private final RecoveryManager recoveryManager;
private final CompositeMessageProcessor messageProcessor;
private final UnifiedOddsStatistics statisticsMBean;
private final SportEntityFactory sportEntityFactory;
private final String processorId;
private final FeedMessageFactory messageFactory;
private final FeedMessageValidator feedMessageValidator;
private final Cache dispatchedFixtureChangesCache;
private OddsFeedListener oddsFeedListener;
private MessageInterest messageInterest;
@Inject
public OddsFeedSessionImpl(SDKInternalConfiguration config,
MessageReceiver messageReceiver,
RecoveryManager recoveryManager,
CompositeMessageProcessor messageProcessor,
SDKProducerManager producerManager,
SportsInfoManager sportsInfoManager,
SportEntityFactory sportEntityFactory,
FeedMessageFactory messageFactory,
FeedMessageValidator feedMessageValidator,
UnifiedOddsStatistics ufStats,
@Named("DispatchedFixturesChangesCache") Cache dispatchedFixtureChangesCache) {
checkNotNull(messageReceiver, "messageReceiver cannot be a null reference");
checkNotNull(recoveryManager, "recoveryManager cannot be a null reference");
checkNotNull(messageProcessor, "messageProcessor cannot be a null reference");
checkNotNull(ufStats, "ufStats cannot be a null reference");
checkNotNull(config, "config cannot be a null reference");
checkNotNull(producerManager, "producerManager cannot be a null reference");
checkNotNull(sportsInfoManager, "sportsInfoManager cannot be a null reference");
checkNotNull(sportEntityFactory, "sportEntityFactory cannot be a null reference");
checkNotNull(messageFactory, "messageFactory cannot be a null reference");
checkNotNull(feedMessageValidator, "feedMessageValidator cannot be a null reference");
checkNotNull(dispatchedFixtureChangesCache);
this.config = config;
this.messageProcessor = messageProcessor;
this.sportsInfoManager = sportsInfoManager;
this.producerManager = producerManager;
this.messageReceiver = messageReceiver;
this.recoveryManager = recoveryManager;
this.statisticsMBean = ufStats;
this.sportEntityFactory = sportEntityFactory;
this.messageFactory = messageFactory;
this.feedMessageValidator = feedMessageValidator;
this.dispatchedFixtureChangesCache = dispatchedFixtureChangesCache;
this.processorId = UUID.randomUUID().toString();
}
public void open(List routingKeys, MessageInterest messageInterest, OddsFeedListener listener) throws IOException {
checkNotNull(routingKeys, "Session routing keys can not be a null reference");
checkNotNull(messageInterest, "oddsInterest cannot be a null reference");
checkNotNull(listener, "listener cannot be a null reference");
checkArgument(!routingKeys.isEmpty(), "session routing keys can not be empty");
this.oddsFeedListener = listener;
this.messageInterest = messageInterest;
messageProcessor.init(this);
messageReceiver.open(routingKeys, this);
logger.info("OddsFeedSession opened(Message interest: {})", messageInterest);
}
/**
* Consumes the provided message
*
* @param unmarshalledMessage - an unmarshalled message payload
* @param body - the raw payload (mainly used for logging and user exposure)
* @param routingKeyInfo - a {@link RoutingKeyInfo} instance describing the message routing key
*/
@Override
public void onMessageReceived(UnmarshalledMessage unmarshalledMessage, byte[] body, RoutingKeyInfo routingKeyInfo, MessageTimestamp timestamp) {
if (isMessageDiscardable(unmarshalledMessage)) {
return;
}
long now = System.currentTimeMillis();
ValidationResult validationResult = feedMessageValidator.validate(unmarshalledMessage, routingKeyInfo);
switch (validationResult) {
case Success:
logger.debug("Message {} successfully validated. ProducerId:{}, EventId:'{}'. Message processing continues", unmarshalledMessage.getClass().getName(), provideProducerIdFromMessage(unmarshalledMessage), provideEventIdFromMessage(unmarshalledMessage));
break;
case ProblemsDetected:
logger.warn("Problems were detected while validating message {}, but the message is still eligible for further processing. ProducerId:{}, EventId:'{}'",
unmarshalledMessage.getClass().getName(), provideProducerIdFromMessage(unmarshalledMessage), provideEventIdFromMessage(unmarshalledMessage));
break;
case Failure:
logger.warn("Validation of message {} failed. Raising onUnparseableMessage event. ProducerId:{}, EventId:'{}'",
unmarshalledMessage.getClass().getName(), provideProducerIdFromMessage(unmarshalledMessage), provideEventIdFromMessage(unmarshalledMessage));
SportEvent event = routingKeyInfo.getEventId() == null ?
null :
getSportEventFor(routingKeyInfo.getEventId().toString(), routingKeyInfo.getSportId());
dispatchUnparsableMessage(body, event, provideProducerIdFromMessage(unmarshalledMessage), timestamp);
return;
default:
logger.error("Validation result '{}' is not supported. Aborting message processing. Type:{} ProducerId:{}, EventId:'{}'",
validationResult, unmarshalledMessage.getClass().getName(), provideProducerIdFromMessage(unmarshalledMessage), provideEventIdFromMessage(unmarshalledMessage));
return;
}
Stopwatch timer = Stopwatch.createStarted();
int producerId = provideProducerIdFromMessage(unmarshalledMessage);
recoveryManager.onMessageProcessingStarted(this.hashCode(), producerId, now);
messageProcessor.processMessage(unmarshalledMessage, body, routingKeyInfo, timestamp);
recoveryManager.onMessageProcessingEnded(this.hashCode(), producerId, provideMessageGenTimestampFromMessage(unmarshalledMessage));
clientInteractionLog.info("Message -> ({}|{}|{}|{}) processing finished on {}, duration: {}",
producerId,
provideEventIdFromMessage(unmarshalledMessage),
unmarshalledMessage.getClass().getSimpleName(),
provideGenTimestampFromMessage(unmarshalledMessage),
getConsumerDescription(),
timer.stop());
statisticsMBean.onMessageReceived(now, System.currentTimeMillis(), unmarshalledMessage);
}
/**
* Dispatches the "unparsable message received event"
*
* @param rawMessage - the raw message payload
* @param eventId - if available the related sport event id; otherwise null
*/
@Override
public void onMessageDeserializationFailed(byte[] rawMessage, URN eventId) {
SportEvent se = null;
if (eventId != null) {
se = eventId.getGroup() == ResourceTypeGroup.TOURNAMENT ? sportsInfoManager.getLongTermEvent(eventId) :
sportsInfoManager.getCompetition(eventId);
}
long time = new TimeUtilsImpl().now();
dispatchUnparsableMessage(rawMessage, se, null, new MessageTimestampImpl(time));
}
/**
* Returns the processor identifier
*
* @return - the processor identifier
*/
@Override
public String getProcessorId() {
return processorId;
}
/**
* Dispatches the processed message to the client
*
* @param o - the message that should be processed
* @param body - the raw body of the received message
* @param routingKeyInfo - a {@link RoutingKeyInfo} instance describing the message routing key
* @param timestamp - all message timestamps
*/
public void processMessage(UnmarshalledMessage o, byte[] body, RoutingKeyInfo routingKeyInfo, MessageTimestamp timestamp) {
// long now = System.currentTimeMillis();
try {
if (o instanceof UFOddsChange) {
UFOddsChange message = (UFOddsChange) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
OddsChange oc = messageFactory.buildOddsChange(se, message, body, timestamp);
oddsFeedListener.onOddsChange(this, oc);
} else if (o instanceof UFBetStop) {
UFBetStop message = (UFBetStop) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
BetStop sdkBetStop = messageFactory.buildBetStop(se, message, body, timestamp);
oddsFeedListener.onBetStop(this, sdkBetStop);
} else if (o instanceof UFBetSettlement) {
UFBetSettlement message = (UFBetSettlement) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
BetSettlement bs = messageFactory.buildBetSettlement(se, message, body, timestamp);
logger.trace("Bet Settlement");
oddsFeedListener.onBetSettlement(this, bs);
} else if (o instanceof UFRollbackBetSettlement) {
UFRollbackBetSettlement message = (UFRollbackBetSettlement) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
RollbackBetSettlement rbs = messageFactory.buildRollbackBetSettlement(se, message, body, timestamp);
oddsFeedListener.onRollbackBetSettlement(this, rbs);
} else if (o instanceof UFBetCancel) {
UFBetCancel message = (UFBetCancel) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
BetCancel cb = messageFactory.buildBetCancel(se, message, body, timestamp);
logger.trace("Bet Cancel");
oddsFeedListener.onBetCancel(this, cb);
} else if (o instanceof UFFixtureChange) {
UFFixtureChange message = (UFFixtureChange) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
FixtureChange fc = messageFactory.buildFixtureChange(se, message, body, timestamp);
logger.trace("Fixture Change");
oddsFeedListener.onFixtureChange(this, fc);
} else if (o instanceof UFRollbackBetCancel) {
UFRollbackBetCancel message = (UFRollbackBetCancel) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
SportEvent se = getSportEventFor(message.getEventId(), routingKeyInfo.getSportId());
RollbackBetCancel rbc = messageFactory.buildRollbackBetCancel(se, message, body, timestamp);
logger.trace("Rollback Bet Cancel");
oddsFeedListener.onRollbackBetCancel(this, rbc);
} else if (o instanceof UFSnapshotComplete) {
UFSnapshotComplete sc = (UFSnapshotComplete) o;
timestamp = new MessageTimestampImpl(sc.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
recoveryManager.onSnapshotCompleteReceived(sc.getProduct(), timestamp.getDispatched(), sc.getRequestId(), messageInterest);
} else if (o instanceof UFAlive) {
logger.trace("Alive");
UFAlive message = (UFAlive) o;
timestamp = new MessageTimestampImpl(message.getTimestamp(), timestamp.getSent(), timestamp.getReceived(), new TimeUtilsImpl().now());
recoveryManager.onAliveReceived(message.getProduct(), message.getTimestamp(), timestamp.getDispatched(), message.getSubscribed() == 1, messageInterest == MessageInterest.SystemAliveMessages);
} else {
logger.warn("Unsupported Message: " + o.getClass().getName());
throw new UnsupportedOperationException("Unsupported message");
}
} catch (Exception re) {
logger.warn("Problems processing a message: \n" + new String(body), re);
dispatchUnparsableMessage(
body,
routingKeyInfo.getEventId() == null ? null : getSportEventFor(routingKeyInfo.getEventId(), routingKeyInfo.getSportId()),
provideProducerIdFromMessage(o),
timestamp);
}
}
/**
* This method should be ignored since the {@link OddsFeedSessionImpl} is the final dispatching message processor.
*
* @param nextMessageProcessor - should be ignored/not used
*/
@Override
public void setNextMessageProcessor(FeedMessageProcessor nextMessageProcessor) {
throw new UnsupportedOperationException("The final dispatching processor cannot have a successor processor");
}
/**
* Returns a {@link String} which describes the consumer
*
* @return - a {@link String} which describes the consumer
*/
@Override
public String getConsumerDescription() {
return "UFSession-" + messageInterest;
}
/**
* Returns the consumer {@link MessageInterest}
*
* @return the consumer {@link MessageInterest}
*/
@Override
public MessageInterest getMessageInterest() {
return messageInterest;
}
private void dispatchUnparsableMessage(byte[] body, SportEvent event, Integer producerId, MessageTimestamp timestamp) {
try {
oddsFeedListener.onUnparseableMessage(
this,
body,
event
);
oddsFeedListener.onUnparsableMessage(this, messageFactory.buildUnparsableMessage(event, producerId, body, timestamp));
} catch (Exception re) {
logger.warn("Problems dispatching onUnparseableMessage(), message body: \n" + new String(body), re);
}
}
private SportEvent getSportEventFor(String eventId, URN sportId) {
URN parsedEventId = URN.parse(eventId);
return getSportEventFor(parsedEventId, sportId);
}
private SportEvent getSportEventFor(URN eventId, URN sportId) {
try {
return sportEntityFactory.buildSportEvent(eventId, sportId, config.getDesiredLocales(), true);
} catch (ObjectNotFoundException e) {
throw new com.sportradar.unifiedodds.sdk.exceptions.ObjectNotFoundException("Error providing the associated event object[" + eventId + "]", e);
}
}
/**
* Check if the provided message can/should be discarded (ex: message from a disabled producer)
*
* @param o - the message object that should be checked
* @return - true
if the message can be discarded, else false
*/
private boolean isMessageDiscardable(UnmarshalledMessage o) {
int producerId = provideProducerIdFromMessage(o);
if (!producerManager.isProducerEnabled(producerId)) {
return true;
}
if (!messageInterest.isProducerInScope(producerManager.getProducer(producerId))) {
return true;
}
if (o instanceof UFFixtureChange) {
String fixtureChangeCacheKey = generateFixtureChangeCacheKey((UFFixtureChange) o);
if (dispatchedFixtureChangesCache.getIfPresent(fixtureChangeCacheKey) == null) {
dispatchedFixtureChangesCache.put(fixtureChangeCacheKey, fixtureChangeCacheKey);
} else {
return true;
}
}
return false;
}
/**
* Returns a built cache key for the provided {@link UFFixtureChange}
*
* @param fixtureChange the object for which the key is needed
* @return a built cache key for the provided {@link UFFixtureChange}
*/
private String generateFixtureChangeCacheKey(UFFixtureChange fixtureChange) {
return fixtureChange.getProduct() + "_" + fixtureChange.getEventId() + "_" + fixtureChange.getTimestamp();
}
/**
* Provides the id of the message producer
*
* @param o - the message from which the producerId should be provided
* @return - the id of the message producer
*/
private int provideProducerIdFromMessage(UnmarshalledMessage o) {
int producerId;
if (o instanceof UFOddsChange) {
producerId = ((UFOddsChange) o).getProduct();
} else if (o instanceof UFBetStop) {
producerId = ((UFBetStop) o).getProduct();
} else if (o instanceof UFBetSettlement) {
producerId = ((UFBetSettlement) o).getProduct();
} else if (o instanceof UFRollbackBetSettlement) {
producerId = ((UFRollbackBetSettlement) o).getProduct();
} else if (o instanceof UFBetCancel) {
producerId = ((UFBetCancel) o).getProduct();
} else if (o instanceof UFFixtureChange) {
producerId = ((UFFixtureChange) o).getProduct();
} else if (o instanceof UFRollbackBetCancel) {
producerId = ((UFRollbackBetCancel) o).getProduct();
} else if (o instanceof UFSnapshotComplete) {
producerId = ((UFSnapshotComplete) o).getProduct();
} else if (o instanceof UFAlive) {
producerId = ((UFAlive) o).getProduct();
} else {
producerId = UnifiedFeedConstants.UNKNOWN_PRODUCER_ID;
}
return producerId;
}
/**
* Provides the id of the associated event if available, otherwise an explanation why
* the eventId is not available get(ex: for a snapshot complete -> system message)
*
* @param o - the message from which the eventIdd should be provided
* @return - the associated eventId or an explanation why the eventId is not available
* (ex: for a snapshot complete -> system message)
*/
private String provideEventIdFromMessage(UnmarshalledMessage o) {
String eventId;
if (o instanceof UFOddsChange) {
eventId = ((UFOddsChange) o).getEventId();
} else if (o instanceof UFBetStop) {
eventId = ((UFBetStop) o).getEventId();
} else if (o instanceof UFBetSettlement) {
eventId = ((UFBetSettlement) o).getEventId();
} else if (o instanceof UFRollbackBetSettlement) {
eventId = ((UFRollbackBetSettlement) o).getEventId();
} else if (o instanceof UFBetCancel) {
eventId = ((UFBetCancel) o).getEventId();
} else if (o instanceof UFFixtureChange) {
eventId = ((UFFixtureChange) o).getEventId();
} else if (o instanceof UFRollbackBetCancel) {
eventId = ((UFRollbackBetCancel) o).getEventId();
} else {
return "System message";
}
return eventId;
}
/**
* Provides the message generation timestamp,
* the generation timestamp is extracted only from the betstop and oddschange message
*
* @param o the message from which the timestamp should be provided
* @return the message generation timestamp if available; otherwise null
*/
private Long provideMessageGenTimestampFromMessage(UnmarshalledMessage o) {
Long timestamp = null;
if (o instanceof UFOddsChange) {
timestamp = ((UFOddsChange) o).getTimestamp();
} else if (o instanceof UFBetStop) {
timestamp = ((UFBetStop) o).getTimestamp();
} else if (o instanceof UFAlive) {
timestamp = ((UFAlive) o).getTimestamp();
}
return timestamp;
}
/**
* Provides the message timestamp
*
* @param o the message from which the timestamp should be provided
* @return the message timestamp
*/
private long provideGenTimestampFromMessage(UnmarshalledMessage o) {
long timestamp;
if (o instanceof UFOddsChange) {
timestamp = ((UFOddsChange) o).getTimestamp();
} else if (o instanceof UFBetStop) {
timestamp = ((UFBetStop) o).getTimestamp();
} else if (o instanceof UFBetSettlement) {
timestamp = ((UFBetSettlement) o).getTimestamp();
} else if (o instanceof UFRollbackBetSettlement) {
timestamp = ((UFRollbackBetSettlement) o).getTimestamp();
} else if (o instanceof UFBetCancel) {
timestamp = ((UFBetCancel) o).getTimestamp();
} else if (o instanceof UFFixtureChange) {
timestamp = ((UFFixtureChange) o).getTimestamp();
} else if (o instanceof UFRollbackBetCancel) {
timestamp = ((UFRollbackBetCancel) o).getTimestamp();
} else if (o instanceof UFSnapshotComplete) {
timestamp = ((UFSnapshotComplete) o).getTimestamp();
} else if (o instanceof UFAlive) {
timestamp = ((UFAlive) o).getTimestamp();
} else {
timestamp = 0;
}
return timestamp;
}
@Override
public int hashCode() {
int result = config != null ? config.hashCode() : 0;
result = 31 * result + (producerManager != null ? producerManager.hashCode() : 0);
result = 31 * result + (sportsInfoManager != null ? sportsInfoManager.hashCode() : 0);
result = 31 * result + (messageReceiver != null ? messageReceiver.hashCode() : 0);
result = 31 * result + (recoveryManager != null ? recoveryManager.hashCode() : 0);
result = 31 * result + (messageProcessor != null ? messageProcessor.hashCode() : 0);
result = 31 * result + (statisticsMBean != null ? statisticsMBean.hashCode() : 0);
result = 31 * result + (sportEntityFactory != null ? sportEntityFactory.hashCode() : 0);
result = 31 * result + (processorId != null ? processorId.hashCode() : 0);
result = 31 * result + (messageFactory != null ? messageFactory.hashCode() : 0);
result = 31 * result + (feedMessageValidator != null ? feedMessageValidator.hashCode() : 0);
result = 31 * result + (dispatchedFixtureChangesCache != null ? dispatchedFixtureChangesCache.hashCode() : 0);
result = 31 * result + (oddsFeedListener != null ? oddsFeedListener.hashCode() : 0);
result = 31 * result + (messageInterest != null ? messageInterest.hashCode() : 0);
return result;
}
}