package org.rpis5.chapters.chapter_07.rxjava2jdbc.wallet; import com.google.common.base.Charsets; import com.google.common.io.Resources; import io.reactivex.Flowable; import io.reactivex.Single; import lombok.extern.slf4j.Slf4j; import org.davidmoten.rx.jdbc.Database; import org.davidmoten.rx.jdbc.tuple.Tuple3; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.Comparator; import static java.lang.String.format; /** * Unfortunately it ides not work. * Transactions are too hard for rxjava2-jdbc :-( */ @Slf4j public class WalletServiceImpl implements WalletService { public static final String SELECT_BY_OWNER = "select id, owner, balance, deposits, withdraws from wallet where owner=:owner"; public static final String UPDATE_WALLET = "update wallet set balance=:balance, withdraws=:withdraws, deposits=:deposits where owner=:owner"; private Database database; public WalletServiceImpl(String dbUri, int poolSize) { this.database = Database.from(dbUri, poolSize); } @Override public Mono<Void> initializeDatabase() { return Mono.fromCallable(() -> { String schema = Resources.toString(Resources.getResource("schema.sql"), Charsets.UTF_8); return database.update(schema) .counts() .blockingLast(); }).then(); } @Override public Flux<String> generateClients(Integer number, Integer defaultBalance) { return Flux.from( database.update("insert into wallet values (?, ?, ?, ?, ?)") .parameterListStream( Flowable .range(0, number) .map(id -> Arrays.asList(id, ownerName(id), defaultBalance, 0, 0)) ).complete() .andThen(Flowable .range(0, number) .map(this::ownerName))); } @Override public Mono<TxResult> transferMoney( Mono<String> fromOwnerMono, Mono<String> toOwnerMono, Mono<Integer> amountMono ) { Flowable<TxResult> rxJavaResult = Single.zip( Single.fromPublisher(fromOwnerMono), Single.fromPublisher(toOwnerMono), Single.fromPublisher(amountMono), Tuple3::new ).flatMapPublisher(tuple -> { String from = tuple._1(); String to = tuple._2(); Integer amount = tuple._3(); return database .select(SELECT_BY_OWNER) .parameter("owner", from) .transacted() .autoMap(WalletData.class) .flatMap(txFrom -> txFrom .select(SELECT_BY_OWNER) .parameter("owner", to) .autoMap(WalletData.class) .flatMap(txTo -> { WalletData fromWallet = txFrom.value(); WalletData toWallet = txTo.value(); if (fromWallet.balance() > amount) { return txTo .update(UPDATE_WALLET) .parameter("owner", from) .parameter("balance", fromWallet.balance() - amount) .parameter("deposits", fromWallet.deposits()) .parameter("withdraws", fromWallet.withdraws() + 1) .counts() .flatMap(updateTx -> updateTx .update(UPDATE_WALLET) .parameter("owner", to) .parameter("balance", toWallet.balance() + amount) .parameter("deposits", toWallet.deposits() + 1) .parameter("withdraws", toWallet.withdraws()) .valuesOnly() .counts() .flatMap(tx -> { log.warn("Transferring from: {}, to {}, amount: {}", fromWallet.owner(), toWallet.owner(), amount); return Flowable.just(TxResult.SUCCESS); }) ).onErrorReturn(e -> TxResult.TX_CONFLICT); } else { return Flowable.just(TxResult.NOT_ENOUGH_FUNDS); } }) ); }); return Mono.from(rxJavaResult); } @Override public Mono<Statistics> reportAllWallets() { return Flux.from(database.select(WalletData.class).get()) .sort(Comparator.comparing(WalletData::owner)) .doOnNext(w -> log.info(format("%10s: %7d$ (d: %5s | w: %5s)", w.owner(), w.balance(), w.deposits(), w.withdraws()))) .reduce(new Statistics(), Statistics::withWallet); } @Override public Mono<Void> removeAllClients() { return Mono .from(database.update("delete from wallet").counts()) .doOnSuccess(count -> log.info("Deleted {} records", count)) .then(); } private String ownerName(Integer id) { return format("client-%05d", id); } }