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

games.mythical.saga.sdk.client.observer.SagaStatusUpdateObserver Maven / Gradle / Ivy

There is a newer version: 1.1.30
Show newest version
package games.mythical.saga.sdk.client.observer;

import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import games.mythical.saga.sdk.client.executor.*;
import games.mythical.saga.sdk.client.model.SagaItem;
import games.mythical.saga.sdk.client.model.SagaItemUpdate;
import games.mythical.saga.sdk.client.model.SagaUserAmount;
import games.mythical.saga.sdk.exception.ErrorData;
import games.mythical.saga.sdk.exception.SagaErrorCode;
import games.mythical.saga.sdk.exception.SagaException;
import games.mythical.saga.sdk.exception.SubError;
import games.mythical.saga.sdk.proto.api.itemtype.FailedItemTypeBatch;
import games.mythical.saga.sdk.proto.streams.StatusUpdate;
import games.mythical.saga.sdk.proto.streams.currency.CurrencyUpdate;
import games.mythical.saga.sdk.proto.streams.currencytype.CurrencyTypeUpdate;
import games.mythical.saga.sdk.proto.streams.item.ItemUpdate;
import games.mythical.saga.sdk.proto.streams.itemtype.ItemTypeUpdate;
import games.mythical.saga.sdk.proto.streams.metadata.MetadataUpdate;
import games.mythical.saga.sdk.proto.streams.playerwallet.PlayerWalletUpdate;
import games.mythical.saga.sdk.proto.streams.reservation.ReservationUpdate;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@Slf4j
public final class SagaStatusUpdateObserver extends AbstractObserver {
    private static final String PING_TRACE = "ping";
    private static SagaStatusUpdateObserver instance;
    private final Consumer resubscribe;
    private SagaCurrencyExecutor sagaCurrencyExecutor;
    private SagaCurrencyTypeExecutor sagaCurrencyTypeExecutor;
    private SagaItemExecutor sagaItemExecutor;
    private SagaItemTypeExecutor sagaItemTypeExecutor;
    private SagaPlayerWalletExecutor sagaPlayerWalletExecutor;
    private SagaReservationExecutor sagaReservationExecutor;
    private SagaMetadataExecutor sagaMetadataExecutor;

    public SagaStatusUpdateObserver(Consumer resubscribe) {
        this.resubscribe = resubscribe;
    }

    public static SagaStatusUpdateObserver getInstance() {
        return instance;
    }

    public static SagaStatusUpdateObserver initialize(Consumer resubscribe) {
        instance = new SagaStatusUpdateObserver(resubscribe);
        return instance;
    }

    public static void clear() {
        instance = null;
    }

    public SagaStatusUpdateObserver with(SagaCurrencyExecutor sagaCurrencyExecutor) {
        this.sagaCurrencyExecutor = sagaCurrencyExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaCurrencyTypeExecutor sagaCurrencyTypeExecutor) {
        this.sagaCurrencyTypeExecutor = sagaCurrencyTypeExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaItemExecutor sagaItemExecutor) {
        this.sagaItemExecutor = sagaItemExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaItemTypeExecutor sagaItemTypeExecutor) {
        this.sagaItemTypeExecutor = sagaItemTypeExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaPlayerWalletExecutor sagaPlayerWalletExecutor) {
        this.sagaPlayerWalletExecutor = sagaPlayerWalletExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaReservationExecutor sagaReservationExecutor) {
        this.sagaReservationExecutor = sagaReservationExecutor;
        return this;
    }

    public SagaStatusUpdateObserver with(SagaMetadataExecutor sagaMetadataExecutor) {
        this.sagaMetadataExecutor = sagaMetadataExecutor;
        return this;
    }

    @Override
    public void onNext(StatusUpdate message) {
        log.trace("StatusUpdateObserver.onNext for event {} with message {}", message.getStatusUpdateCase(), message.getTraceId());
        resetConnectionRetry();
        if (message.getTraceId().equalsIgnoreCase(PING_TRACE)) {
            return;
        }
        try {
            switch (message.getStatusUpdateCase()) {
                case CURRENCY_UPDATE:
                    handleCurrencyUpdate(message.getCurrencyUpdate(), message.getTraceId());
                    break;
                case CURRENCY_TYPE_UPDATE:
                    handleCurrencyTypeUpdate(message.getCurrencyTypeUpdate(), message.getTraceId());
                    break;
                case ITEM_UPDATE:
                    handleItemUpdate(message.getItemUpdate(), message.getTraceId());
                    break;
                case METADATA_UPDATE:
                    handleMetadataUpdate(message.getMetadataUpdate(), message.getTraceId());
                    break;
                case ITEM_TYPE_UPDATE:
                    handleItemTypeUpdate(message.getItemTypeUpdate(), message.getTraceId());
                    break;
                case PLAYER_WALLET_UPDATE:
                    handlePlayerWalletUpdate(message.getPlayerWalletUpdate(), message.getTraceId());
                    break;
                case RESERVATION_UPDATE:
                    handleReservationUpdate(message.getReservationUpdate(), message.getTraceId());
                    break;
                default: {
                    log.error("Unrecognized event {}", message.getStatusUpdateCase());
                    throw new SagaException(SagaErrorCode.UNRECOGNIZED);
                }
            }
        } catch (Exception e) {
            log.error("Exception calling executor action for message:{}. {}", message.getTraceId(), e);
        }
    }

    @Override
    public void onError(Throwable t) {
        log.error("StatusUpdateObserver.onError", t);
        sleepBetweenReconnects();
        resubscribe.accept(this);
    }

    @Override
    public void onCompleted() {
        log.info("StatusUpdateObserver stream closed");
        sleepBetweenReconnects();
        resubscribe.accept(this);
    }

    private void handleCurrencyUpdate(CurrencyUpdate update, String traceId) throws Exception {
        if (sagaCurrencyExecutor == null) {
            log.debug("Currency update received, but no currency executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaCurrencyExecutor.onError(toErrData(error));
            } else {
                final var message = update.getStatusUpdate();
                if (message.getBalancesList().isEmpty()) {
                    log.error("No balances were issued, this shouldn't have occurred. {}", traceId);
                    return;
                }

                switch (message.getCurrencyState()) {
                    case ISSUED:
                        var balances = message.getBalancesList().stream()
                                .map(balance -> new SagaUserAmount(balance.getOauthId(), balance.getBalanceInWei()))
                                .collect(Collectors.toList());
                        sagaCurrencyExecutor.currencyIssued(
                                message.getBalancesList().get(0).getCurrencyTypeId(),
                                message.getTransactionId(),
                                balances,
                                message.getIdempotencyId(),
                                traceId
                        );
                        break;
                    default:
                        var balance = message.getBalancesList().stream().findFirst().get();
                        sagaCurrencyExecutor.updateCurrency(
                                balance.getCurrencyTypeId(),
                                message.getTransactionId(),
                                balance.getOauthId(),
                                balance.getBalanceInWei(),
                                message.getIdempotencyId(),
                                traceId,
                                message.getCurrencyState()
                        );
                        break;
                }
            }
        }
    }

    private void handleCurrencyTypeUpdate(CurrencyTypeUpdate update, String traceId) throws Exception {
        if (sagaCurrencyTypeExecutor == null) {
            log.debug("CurrencyType update received, but no currencytype executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaCurrencyTypeExecutor.onError(toErrData(error));
            } else {
                final var message = update.getStatusUpdate();
                sagaCurrencyTypeExecutor.updateCurrencyType(
                        message.getCurrencyTypeId(),
                        message.getTransactionId(),
                        message.getContractAddress(),
                        message.getIdempotencyId(),
                        traceId,
                        message.getCurrencyTypeState()
                );
            }
        }
    }

    private void handleItemUpdate(ItemUpdate update, String traceId) throws Exception {
        if (sagaItemExecutor == null) {
            log.debug("Item update received, but no item executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaItemExecutor.onError(toErrData(error));
            } else if (update.hasStatusUpdate()) {
                final var message = update.getStatusUpdate();
                sagaItemExecutor.updateItem(
                    message.getInventoryId(),
                    message.getItemTypeId(),
                    message.getOauthId(),
                    message.getTokenId(),
                    message.getMetadataUrl(),
                    traceId,
                    message.getItemState()
                );
            } else {
                final var updates = update.getStatusUpdates().getStatusUpdatesList().stream()
                        .map(SagaItemUpdate::fromProto).collect(Collectors.toList());
                sagaItemExecutor.updateItems(updates, traceId);
            }
        }
    }

    private void handleMetadataUpdate(MetadataUpdate update, String traceId) throws Exception {
        if (sagaMetadataExecutor == null) {
            log.debug("Metadata update received, but no metadata executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaItemExecutor.onError(toErrData(error));
            } else {
                final var message = update.getMetadataUpdated();
                sagaMetadataExecutor.updateMetadata(
                        message.getInventoryId(),
                        message.getMetadataUrl(),
                        traceId
                );
            }
        }
    }

    private void handleItemTypeUpdate(ItemTypeUpdate update, String traceId) throws Exception {
        if (sagaItemTypeExecutor == null) {
            log.debug("Item type update received, but no item type executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaItemTypeExecutor.onError(toErrData(error));
            } else {
                final var message = update.getStatusUpdate();
                sagaItemTypeExecutor.updateItemType(
                    message.getItemTypeId(),
                    traceId,
                    message.getItemTypeState()
                );
            }
        }
    }

    private void handlePlayerWalletUpdate(PlayerWalletUpdate update, String traceId) throws Exception {
        if (sagaPlayerWalletExecutor == null) {
            log.debug("Player wallet update received, but no player wallet executor registered {}", update);
        }
        else {
            if (update.hasError()) {
                final var error = update.getError();
                sagaPlayerWalletExecutor.onError(toErrData(error));
            } else {
                final var statusUpdate = update.getStatusUpdate();
                sagaPlayerWalletExecutor.updatePlayerWallet(traceId, statusUpdate.getOauthId());
            }
        }
    }

    private void handleReservationUpdate(ReservationUpdate update, String traceId) {
        if (sagaReservationExecutor == null) {
            log.debug("Reservation update received, but no reservation executor registered {}", update);
        } else {
            switch (update.getUpdateCase()) {
                case ERROR:
                    sagaReservationExecutor.onError(toErrData(update.getError()));
                    break;
                case RESERVATION_CREATED:
                    sagaReservationExecutor.onReservationCreated(update.getReservationCreated().getReservationId(), traceId);
                    break;
                case RESERVATION_RELEASED:
                    sagaReservationExecutor.onReservationReleased(update.getReservationReleased().getReservationId(), traceId);
                    break;
                case RESERVATION_REDEEMED:
                    final var items = update.getReservationRedeemed().getItemsList().stream()
                            .map(SagaItem::fromProto).collect(Collectors.toList());
                    final var failedBatches = update.getReservationRedeemed().getFailedBatchesList().stream()
                            .map(FailedItemTypeBatch::getItemTypeId).collect(Collectors.toList());
                    sagaReservationExecutor.onReservationRedeemed(update.getReservationRedeemed().getReservationId(), items, failedBatches, traceId);
                    break;
                default:
                    log.error("Unknown reservation update: {}", update.getUpdateCase());
                    break;
            }
        }
    }

    private Map toMetadata(Struct protoMetadata) {
        final var metadataMap = new HashMap();
        protoMetadata.getFieldsMap()
            .forEach((key, value) -> metadataMap.put(key, metadataValueToString(value)));
        return metadataMap;
    }

    private String metadataValueToString(Value value) {
        if (value.hasNullValue()) {
            return null;
        } else if (value.hasStringValue()) {
            return value.getStringValue();
        }
        return value.toString();
    }

    private SubError toSubError(games.mythical.saga.sdk.proto.common.SubError error) {
        return SubError.builder()
            .code(error.getErrorCode())
            .message(error.getMessage())
            .source(error.getSource())
            .metadata(toMetadata(error.getMetadata()))
            .build();
    }

    private ErrorData toErrData(games.mythical.saga.sdk.proto.common.ErrorData error) {
        final var subErrors = error.getSuberrorsList().stream()
            .map(this::toSubError)
            .collect(Collectors.toList());

        return ErrorData.builder()
            .code(error.getErrorCode())
            .trace(error.getTrace())
            .source(error.getSource())
            .message(error.getMessage())
            .metadata(toMetadata(error.getMetadata()))
            .suberrors(subErrors)
            .build();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy