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

com.github.robozonky.common.remote.Zonky Maven / Gradle / Ivy

/*
 * Copyright 2019 The RoboZonky Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.robozonky.common.remote;

import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.ws.rs.core.Response;

import com.github.robozonky.api.remote.CollectionsApi;
import com.github.robozonky.api.remote.ControlApi;
import com.github.robozonky.api.remote.LoanApi;
import com.github.robozonky.api.remote.ParticipationApi;
import com.github.robozonky.api.remote.PortfolioApi;
import com.github.robozonky.api.remote.ReservationApi;
import com.github.robozonky.api.remote.TransactionApi;
import com.github.robozonky.api.remote.WalletApi;
import com.github.robozonky.api.remote.entities.BlockedAmount;
import com.github.robozonky.api.remote.entities.LastPublishedLoan;
import com.github.robozonky.api.remote.entities.Participation;
import com.github.robozonky.api.remote.entities.PurchaseRequest;
import com.github.robozonky.api.remote.entities.RawDevelopment;
import com.github.robozonky.api.remote.entities.RawInvestment;
import com.github.robozonky.api.remote.entities.RawLoan;
import com.github.robozonky.api.remote.entities.ReservationPreferences;
import com.github.robozonky.api.remote.entities.ResolutionRequest;
import com.github.robozonky.api.remote.entities.Resolutions;
import com.github.robozonky.api.remote.entities.Restrictions;
import com.github.robozonky.api.remote.entities.SellRequest;
import com.github.robozonky.api.remote.entities.Statistics;
import com.github.robozonky.api.remote.entities.Transaction;
import com.github.robozonky.api.remote.entities.Wallet;
import com.github.robozonky.api.remote.entities.ZonkyApiToken;
import com.github.robozonky.api.remote.entities.sanitized.Development;
import com.github.robozonky.api.remote.entities.sanitized.Investment;
import com.github.robozonky.api.remote.entities.sanitized.Loan;
import com.github.robozonky.api.remote.entities.sanitized.MarketplaceLoan;
import com.github.robozonky.api.remote.entities.sanitized.Reservation;
import com.github.robozonky.api.remote.enums.Resolution;
import com.github.robozonky.api.remote.enums.TransactionCategory;
import com.github.robozonky.internal.api.Settings;
import com.github.robozonky.internal.test.DateUtil;
import com.github.rutledgepaulv.pagingstreams.PagingStreams;
import jdk.jfr.Event;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * Represents an instance of Zonky API that is fully authenticated and ready to perform operations on behalf of the
 * user. Consider {@link #logout()} when done.
 */
public class Zonky {

    private static final Logger LOGGER = LogManager.getLogger(Zonky.class);

    private final Api controlApi;
    private final Api exports;
    private final Api reservationApi;
    private final PaginatedApi loanApi;
    private final PaginatedApi participationApi;
    private final PaginatedApi portfolioApi;
    private final PaginatedApi walletApi;
    private final PaginatedApi transactions;
    private final PaginatedApi collectionsApi;

    Zonky(final ApiProvider api, final Supplier tokenSupplier) {
        this.controlApi = api.control(tokenSupplier);
        this.exports = api.exports(tokenSupplier);
        this.loanApi = api.marketplace(tokenSupplier);
        this.reservationApi = api.reservations(tokenSupplier);
        this.participationApi = api.secondaryMarketplace(tokenSupplier);
        this.portfolioApi = api.portfolio(tokenSupplier);
        this.walletApi = api.wallet(tokenSupplier);
        this.transactions = api.transactions(tokenSupplier);
        this.collectionsApi = api.collections(tokenSupplier);
    }

    private static  Stream getStream(final PaginatedApi api, final Function> function) {
        return getStream(api, function, Settings.INSTANCE.getDefaultApiPageSize());
    }

    private static  Stream getStream(final PaginatedApi api, final Function> function,
                                              final int pageSize) {
        return getStream(api, function, new Select(), pageSize);
    }

    private static  Stream getStream(final PaginatedApi api, final Function> function,
                                              final Select select) {
        return getStream(api, function, select, Settings.INSTANCE.getDefaultApiPageSize());
    }

    private static  Stream getStream(final PaginatedApi api, final Function> function,
                                              final Select select, final int pageSize) {
        return PagingStreams.streamBuilder(new EntityCollectionPageSource<>(api, function, select, pageSize))
                .pageSize(pageSize)
                .build();
    }

    public void invest(final Investment investment) {
        LOGGER.debug("Investing into loan #{}.", investment.getLoanId());
        controlApi.run(api -> api.invest(new RawInvestment(investment)));
    }

    public void cancel(final Investment investment) {
        LOGGER.debug("Cancelling offer to sell investment in loan #{}.", investment.getLoanId());
        controlApi.run(api -> api.cancel(investment.getId()));
    }

    public void purchase(final Participation participation) {
        LOGGER.debug("Purchasing participation #{} in loan #{}.", participation.getId(), participation.getLoanId());
        controlApi.run(api -> api.purchase(participation.getId(), new PurchaseRequest(participation)));
    }

    public void sell(final Investment investment) {
        LOGGER.debug("Offering to sell investment in loan #{}.", investment.getLoanId());
        controlApi.run(api -> api.offer(new SellRequest(new RawInvestment(investment))));
    }

    public void accept(final Reservation reservation) {
        final ResolutionRequest r = new ResolutionRequest(reservation.getMyReservation().getId(), Resolution.ACCEPTED);
        final Resolutions rs = new Resolutions(Collections.singleton(r));
        controlApi.run(c -> c.accept(rs));
    }

    public void setReservationPreferences(final ReservationPreferences preferences) {
        controlApi.run(c -> c.setReservationPreferences(preferences));
    }

    public Wallet getWallet() {
        return walletApi.execute(WalletApi::wallet);
    }

    /**
     * Retrieve reservations that the user has to either accept or reject.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getPendingReservations() {
        return reservationApi.call(ReservationApi::items)
                .getReservations()
                .stream()
                .map(Reservation::sanitized);
    }

    /**
     * Retrieve blocked amounts from user's wallet via {@link WalletApi}.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getBlockedAmounts() {
        return getStream(walletApi, WalletApi::items);
    }

    /**
     * Retrieve investments from user's portfolio via {@link PortfolioApi}, in a given order.
     * @param select Rules to filter the selection by.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getInvestments(final Select select) {
        final Function investmentDateSupplier = i -> {
            /*
             * Zonky makes it very difficult to figure out when any particular investment was made. this code attempts
             * to figure it out.
             *
             * we find the first payment from past transactions. if there's no first payment, we use the expected date
             * or days past due.
             *
             * we subtract a month from that value to find out the approximate date when this loan was created.
             */
            final Supplier expectedPayment = () -> i.getNextPaymentDate()
                    .map(OffsetDateTime::toLocalDate)
                    .orElse(DateUtil.localNow().toLocalDate()).minusDays(i.getDaysPastDue());
            final LocalDate lastPayment = getTransactions(i)
                    .filter(t -> t.getCategory() == TransactionCategory.PAYMENT)
                    .map(Transaction::getTransactionDate)
                    .sorted()
                    .findFirst()
                    .orElseGet(expectedPayment);
            final LocalDate d = lastPayment.minusMonths(1);
            LOGGER.debug("Date for investment #{} (loan #{}) was determined to be {}.", i.getId(), i.getLoanId(), d);
            return d;
        };
        return getStream(portfolioApi, PortfolioApi::items, select)
                .map(raw -> Investment.sanitized(raw, investmentDateSupplier));
    }

    public Stream getDelinquentInvestments() {
        return getInvestments(new Select()
                                      .in("loan.status", "ACTIVE", "PAID_OFF")
                                      .equals("loan.unpaidLastInst", "true")
                                      .equals("status", "ACTIVE"));
    }

    public Loan getLoan(final int id) {
        final Event event = new ZonkyLoanCallJfrEvent(); // may be triggered by the strategy and we want to measure that
        try {
            event.begin();
            return Loan.sanitized(loanApi.execute(api -> api.item(id)));
        } finally {
            event.commit();
        }
    }

    public Optional getInvestment(final long id) {
        final Select s = new Select().equals("id", id);
        return getInvestments(s).findFirst();
    }

    public Optional getInvestmentByLoanId(final int loanId) {
        final Select s = new Select().equals("loan.id", loanId);
        return getInvestments(s).findFirst();
    }

    public LastPublishedLoan getLastPublishedLoanInfo() {
        return loanApi.execute(LoanApi::lastPublished);
    }

    /**
     * Retrieve loans from marketplace via {@link LoanApi}.
     * @param select Rules to filter the selection by.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getAvailableLoans(final Select select) {
        return getStream(loanApi, LoanApi::items, select).map(MarketplaceLoan::sanitized);
    }

    /**
     * Retrieve transactions from the wallet via {@link TransactionApi}.
     * @param select Rules to filter the selection by.
     * @return All items from the remote API, lazy-loaded, filtered.
     */
    public Stream getTransactions(final Select select) {
        return getStream(transactions, TransactionApi::items, select);
    }

    /**
     * Retrieve transactions from the wallet via {@link TransactionApi}.
     * @param investment Investment to filter the selection by.
     * @return All items from the remote API, lazy-loaded, filtered for the specific investment.
     */
    public Stream getTransactions(final Investment investment) {
        final Select select = new Select().equals("investment.id", investment.getId());
        return getTransactions(select);
    }

    /**
     * Retrieve loan collections information via {@link CollectionsApi}.
     * @param loanId Loan in question.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getDevelopments(final int loanId) {
        return getStream(collectionsApi, a -> a.items(loanId)).map(Development::sanitized);
    }

    /**
     * Retrieve participations from secondary marketplace via {@link ParticipationApi}.
     * @param select Rules to filter the selection by.
     * @return All items from the remote API, lazy-loaded.
     */
    public Stream getAvailableParticipations(final Select select) {
        return getStream(participationApi, ParticipationApi::items, select);
    }

    public ReservationPreferences getReservationPreferences() {
        return reservationApi.call(ReservationApi::preferences);
    }

    public void requestWalletExport() {
        exports.run(ExportApi::requestWalletExport);
    }

    public void requestInvestmentsExport() {
        exports.run(ExportApi::requestInvestmentsExport);
    }

    public Response downloadWalletExport() {
        return exports.call(ExportApi::downloadWalletExport);
    }

    public Response downloadInvestmentsExport() {
        return exports.call(ExportApi::downloadInvestmentsExport);
    }

    public Restrictions getRestrictions() {
        return controlApi.call(ControlApi::restrictions);
    }

    public Statistics getStatistics() {
        return portfolioApi.execute(PortfolioApi::item);
    }

    public void logout() {
        controlApi.run(ControlApi::logout);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy