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

com.github.robozonky.app.tenant.StrategyProvider Maven / Gradle / Ivy

/*
 * Copyright 2021 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.app.tenant;

import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.github.robozonky.api.strategies.InvestmentStrategy;
import com.github.robozonky.api.strategies.PurchaseStrategy;
import com.github.robozonky.api.strategies.ReservationStrategy;
import com.github.robozonky.api.strategies.SellStrategy;
import com.github.robozonky.internal.async.ReloadListener;
import com.github.robozonky.internal.async.Reloadable;
import com.github.robozonky.internal.extensions.StrategyLoader;
import com.github.robozonky.internal.util.StringUtil;
import com.github.robozonky.internal.util.UrlUtil;

class StrategyProvider implements ReloadListener {

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

    private final AtomicReference lastLoadedStrategy = new AtomicReference<>();
    private final AtomicReference toInvest = new AtomicReference<>();
    private final AtomicReference toSell = new AtomicReference<>();
    private final AtomicReference toPurchase = new AtomicReference<>();
    private final AtomicReference forReservations = new AtomicReference<>();
    private final Reloadable reloadableStrategy;

    StrategyProvider(final String strategyLocation) {
        this.reloadableStrategy = Reloadable.with(() -> readStrategy(strategyLocation))
            .addListener(this)
            .reloadAfter(Duration.ofHours(1))
            .async()
            .build();
    }

    StrategyProvider() {
        this.reloadableStrategy = null;
    }

    private static String readStrategy(final String strategyLocation) {
        try (var inputStream = UrlUtil.open(UrlUtil.toURL(strategyLocation))
            .getInputStream()) {
            return StringUtil.toString(inputStream);
        } catch (Exception ex) {
            LOGGER.error("Failed reading strategy source.", ex);
            throw new IllegalStateException("Failed reading strategy source.", ex);
        }
    }

    public static StrategyProvider createFor(final String strategyLocation) {
        return new StrategyProvider(strategyLocation);
    }

    public static StrategyProvider empty() { // for testing purposes only
        return new StrategyProvider();
    }

    private static  T set(final AtomicReference ref, final Supplier> provider, final String desc) {
        final T value = ref.updateAndGet(old -> provider.get()
            .orElse(null));
        if (Objects.isNull(value)) {
            LOGGER.info("{} strategy inactive or missing, functionality disabled.", desc);
        } else {
            LOGGER.info("{} strategy correctly loaded.", desc);
        }
        return value;
    }

    @Override
    public void newValue(final String newValue) {
        var oldStrategy = lastLoadedStrategy.getAndSet(newValue);
        if (Objects.equals(oldStrategy, newValue)) {
            LOGGER.debug("No change in strategy source code detected.");
            return;
        }
        LOGGER.trace("Loading strategies.");
        var investStrategy = set(toInvest, () -> StrategyLoader.toInvest(newValue), "Primary marketplace investment");
        var purchaseStrategy = set(toPurchase, () -> StrategyLoader.toPurchase(newValue),
                "Secondary marketplace purchase");
        var sellingStrategy = set(toSell, () -> StrategyLoader.toSell(newValue), "Portfolio selling");
        var reservationStrategy = set(forReservations, () -> StrategyLoader.forReservations(newValue),
                "Loan reservation confirmation");
        var allStrategiesMissing = Stream.of(investStrategy, purchaseStrategy, sellingStrategy, reservationStrategy)
            .allMatch(Objects::isNull);
        if (allStrategiesMissing) {
            LOGGER.warn("No strategies are available, all operations are disabled. Check log for parser errors.");
        }
        LOGGER.trace("Finished.");
    }

    @Override
    public void valueUnset() {
        lastLoadedStrategy.set(null);
        Stream.of(toInvest, toSell, toPurchase, forReservations)
            .forEach(ref -> ref.set(null));
        LOGGER.warn("There are no strategies, all operations are disabled.");
    }

    private  T loadStrategy(Supplier supplier) {
        if (reloadableStrategy != null) {
            reloadableStrategy.get();
        }
        return supplier.get();
    }

    public Optional getToInvest() {
        return Optional.ofNullable(loadStrategy(toInvest::get));
    }

    public Optional getToSell() {
        return Optional.ofNullable(loadStrategy(toSell::get));
    }

    public Optional getToPurchase() {
        return Optional.ofNullable(loadStrategy(toPurchase::get));
    }

    public Optional getForReservations() {
        return Optional.ofNullable(loadStrategy(forReservations::get));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy