burstcoin.observer.service.AssetService.java Source code

Java tutorial

Introduction

Here is the source code for burstcoin.observer.service.AssetService.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 by luxe - https://github.com/de-luxe - BURST-LUXE-RED2-G6JW-H4HG5
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

package burstcoin.observer.service;

import burstcoin.observer.ObserverProperties;
import burstcoin.observer.bean.AssetBean;
import burstcoin.observer.bean.AssetCandleStickBean;
import burstcoin.observer.event.AssetUpdateEvent;
import burstcoin.observer.service.model.State;
import burstcoin.observer.service.model.asset.Asset;
import burstcoin.observer.service.model.asset.Assets;
import burstcoin.observer.service.model.asset.Order;
import burstcoin.observer.service.model.asset.OrderType;
import burstcoin.observer.service.model.asset.Orders;
import burstcoin.observer.service.model.asset.Trade;
import burstcoin.observer.service.model.asset.Trades;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@Component
public class AssetService {
    private static Log LOG = LogFactory.getLog(AssetService.class);

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private HttpClient httpClient;

    @Autowired
    private ApplicationEventPublisher publisher;

    private Timer timer = new Timer();

    @PostConstruct
    private void postConstruct() {
        LOG.info("Started repeating 'check assets' task.");
        startCheckAssetsTask();
    }

    private void startCheckAssetsTask() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    LOG.info("START import asset data from " + ObserverProperties.getWalletUrl());
                    Map<String, Asset> assetLookup = createAssetLookup();
                    Map<OrderType, Map<Asset, List<Order>>> orderLookup = createOrderLookup(assetLookup);
                    Map<Asset, List<Trade>> tradeLookup = getTradeLookup(assetLookup);

                    State state = getState();
                    LOG.info("FINISH import asset data!");

                    List<AssetBean> assetBeans = new ArrayList<>();
                    List<AssetCandleStickBean> assetCandleStickBeans = new ArrayList<>();
                    for (Asset asset : assetLookup.values()) {
                        List<List> candleStickData = new ArrayList<List>();
                        long volume7Days = 0L;
                        long volume30Days = 0L;
                        String lastPrice = "";
                        List<Trade> trades = tradeLookup.get(asset);
                        if (trades != null && !trades.isEmpty()) {
                            Iterator<Trade> iterator = trades.iterator();
                            boolean withinLast30Days = true;

                            while (withinLast30Days && iterator.hasNext()) {
                                Trade trade = iterator.next();
                                if (StringUtils.isEmpty(lastPrice)) {
                                    lastPrice = convertPrice(trade.getPriceNQT(), trade.getDecimals());
                                }

                                Integer bidOrderBlock = Integer.valueOf(trade.getBidOrderHeight());
                                Integer askOrderBlock = Integer.valueOf(trade.getAskOrderHeight());
                                int block = bidOrderBlock >= askOrderBlock ? bidOrderBlock : askOrderBlock;
                                withinLast30Days = state.getNumberOfBlocks() - 360 * 30 < block;

                                if (withinLast30Days) {
                                    long volume = Long.valueOf(trade.getPriceNQT())
                                            * Long.valueOf(trade.getQuantityQNT());
                                    volume30Days += volume;

                                    if (state.getNumberOfBlocks() - 360 * 7 < block) {
                                        volume7Days += volume;
                                    }
                                }
                            }

                            Long currentBlockHeight = Long.valueOf(state.getNumberOfBlocks());

                            for (int i = 1; i <= 60 /*days*/; i++) {
                                List<Trade> tradesOfDay = new ArrayList<Trade>();
                                for (Trade trade : trades) {
                                    if (trade.getHeight() > currentBlockHeight - (360 * (i + 1))
                                            && trade.getHeight() < currentBlockHeight - (360 * i)) {
                                        tradesOfDay.add(trade);
                                    }
                                }

                                Double min = null;
                                Double max = null;
                                Double first = null;
                                Double last = null;

                                for (Trade trade : tradesOfDay) {
                                    double price = Double
                                            .valueOf(convertPrice(trade.getPriceNQT(), trade.getDecimals()));
                                    if (first == null) {
                                        first = price;
                                    }
                                    if (min == null || price < min) {
                                        min = price;
                                    }
                                    if (max == null || price > max) {
                                        max = price;
                                    }
                                    if (tradesOfDay.indexOf(trade) == tradesOfDay.size() - 1) {
                                        last = price;
                                    }
                                }

                                if (min != null && max != null && first != null && last != null) {
                                    List x = Arrays.asList("" + i, min, first, last, max);
                                    candleStickData.add(x);
                                } else {
                                    candleStickData.add(Arrays.asList("" + i, null, null, null, null));
                                }
                            }
                        }

                        Collections.reverse(candleStickData);

                        List<Order> sellOrders = orderLookup.get(OrderType.ASK).get(asset) != null
                                ? orderLookup.get(OrderType.ASK).get(asset)
                                : new ArrayList<>();
                        List<Order> buyOrders = orderLookup.get(OrderType.BID).get(asset) != null
                                ? orderLookup.get(OrderType.BID).get(asset)
                                : new ArrayList<>();

                        if (!(buyOrders.isEmpty() && sellOrders.isEmpty() && asset.getNumberOfTrades() < 2)) {
                            assetBeans.add(new AssetBean(asset.getAsset(), asset.getName(), asset.getDescription(),
                                    asset.getAccountRS(), asset.getAccount(), asset.getQuantityQNT(),
                                    asset.getDecimals(), asset.getNumberOfAccounts(), asset.getNumberOfTransfers(),
                                    asset.getNumberOfTrades(), buyOrders.size(), sellOrders.size(),
                                    formatAmountNQT(volume7Days, 8), formatAmountNQT(volume30Days, 8), lastPrice));
                            assetCandleStickBeans.add(new AssetCandleStickBean(asset.getAsset(), candleStickData));
                        }
                    }
                    Collections.sort(assetBeans, new Comparator<AssetBean>() {
                        @Override
                        public int compare(AssetBean o1, AssetBean o2) {
                            return Long.valueOf(o2.getVolume30Days()).compareTo(Long.valueOf(o1.getVolume30Days()));
                        }
                    });
                    Collections.sort(assetBeans, new Comparator<AssetBean>() {
                        @Override
                        public int compare(AssetBean o1, AssetBean o2) {
                            return Long.valueOf(o2.getVolume7Days()).compareTo(Long.valueOf(o1.getVolume7Days()));
                        }
                    });

                    // delete data of candleStick for all after index 24 todo remove as soon ui has show/hide charts per asset
                    List<String> assetOrder = new ArrayList<String>();
                    for (AssetBean assetBean : assetBeans) {
                        assetOrder.add(assetBean.getAsset());
                    }
                    assetCandleStickBeans.sort(new Comparator<AssetCandleStickBean>() {
                        @Override
                        public int compare(AssetCandleStickBean o1, AssetCandleStickBean o2) {
                            return ((Integer) assetOrder.indexOf(o1.getAsset()))
                                    .compareTo(assetOrder.indexOf(o2.getAsset()));
                        }
                    });

                    publisher.publishEvent(new AssetUpdateEvent(assetBeans, assetCandleStickBeans));
                } catch (Exception e) {
                    LOG.error("Failed update assets!", e);
                }
            }
        }, 200, ObserverProperties.getAssetRefreshInterval());
    }

    private String convertPrice(String priceString, int decimals) {
        BigInteger price = new BigInteger(priceString);
        BigInteger amount = price.multiply(new BigInteger("" + (long) Math.pow(10, decimals)));
        String negative = "";
        String afterComma = "";
        String fractionalPart = amount.mod(new BigInteger("100000000")).toString();
        amount = amount.divide(new BigInteger("100000000"));
        if (amount.compareTo(BigInteger.ZERO) < 0) {
            amount = amount.abs();
            negative = "-";
        }
        if (!fractionalPart.equals("0")) {
            afterComma = ".";
            for (int i = fractionalPart.length(); i < 8; i++) {
                afterComma += "0";
            }
            afterComma += fractionalPart.replace("0+$", "");
        }
        String result = negative + amount + afterComma;
        while (result.lastIndexOf("0") == result.length() - 1 && result.contains(".")) {
            result = result.substring(0, result.length() - 1);
        }
        if (result.lastIndexOf(".") == result.length() - 1) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    // without decimal
    private String formatAmountNQT(Long amount, int decimals) {
        String amountStr = String.valueOf(amount);
        return amount != null && amountStr.length() >= decimals
                ? amountStr.substring(0, amountStr.length() - decimals)
                : "" + 0;
    }

    private Map<String, Asset> createAssetLookup() {
        Map<String, Asset> assetLookup = new HashMap<>();
        try {
            ContentResponse response = httpClient
                    .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getAllAssets")
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send();

            Assets assets = objectMapper.readValue(response.getContentAsString(), Assets.class);
            LOG.info("received '" + assets.getAssets().size() + "' assets in '" + assets.getRequestProcessingTime()
                    + "' ms");
            assetLookup = new HashMap<>();
            for (Asset asset : assets.getAssets()) {
                assetLookup.put(asset.getAsset(), asset);
            }
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getAllAssets': " + e.getMessage());
        }
        return assetLookup;
    }

    private Map<Asset, List<Trade>> getTradeLookup(Map<String, Asset> assetLookup) {
        Map<Asset, List<Trade>> tradeLookup = new HashMap<>();
        boolean hasMoreTransactions = true;
        int offset = 0;
        int transactionsPerRequest = 1999;
        while (hasMoreTransactions) {
            hasMoreTransactions = updateTradeLookup(tradeLookup, assetLookup, offset, transactionsPerRequest);
            offset += transactionsPerRequest;
        }
        return tradeLookup;
    }

    private boolean updateTradeLookup(Map<Asset, List<Trade>> tradeLookup, Map<String, Asset> assetLookup,
            int offset, int transactionsPerRequest) {
        boolean hasMoreTrades = false;
        try {
            InputStreamResponseListener listener = new InputStreamResponseListener();

            Request request = httpClient
                    .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getAllTrades")
                    .param("firstIndex", String.valueOf(offset))
                    .param("lastIndex", String.valueOf(offset + transactionsPerRequest))
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS);
            request.send(listener);

            Response response = listener.get(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS);

            // Look at the response
            if (response.getStatus() == 200) {
                // Use try-with-resources to close input stream.
                try (InputStream responseContent = listener.getInputStream()) {
                    Trades trades = objectMapper.readValue(responseContent, Trades.class);
                    if (!trades.getTrades().isEmpty() && trades.getTrades().size() >= transactionsPerRequest) {
                        hasMoreTrades = true;
                    }

                    for (Trade trade : trades.getTrades()) {
                        Asset asset = assetLookup.get(trade.getAsset());
                        if (!tradeLookup.containsKey(asset)) {
                            tradeLookup.put(asset, new ArrayList<>());
                        }
                        tradeLookup.get(asset).add(trade);
                    }

                    LOG.info("received '" + trades.getTrades().size() + "' trades in '"
                            + trades.getRequestProcessingTime() + "' ms");
                } catch (Exception e) {
                    LOG.error("Failed to receive faucet account transactions.");
                }
            }
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getAllTrades': " + e.getMessage());
        }
        return hasMoreTrades;
    }

    private State getState() {
        State state = null;
        try {
            InputStreamResponseListener listener = new InputStreamResponseListener();

            Request request = httpClient
                    .newRequest(
                            ObserverProperties.getWalletUrl() + "/burst?requestType=getState&includeCounts=true")
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS);
            request.send(listener);

            Response response = listener.get(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS);
            if (response.getStatus() == 200) {
                try (InputStream responseContent = listener.getInputStream()) {
                    state = objectMapper.readValue(responseContent, State.class);
                } catch (Exception e) {
                    LOG.error("Failed to receive faucet account transactions.");
                }
            }
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getAllTrades': " + e.getMessage());
        }
        return state;
    }

    private Map<OrderType, Map<Asset, List<Order>>> createOrderLookup(Map<String, Asset> assetLookup) {
        Map<OrderType, Map<Asset, List<Order>>> orderLookup = new HashMap<>();
        try {
            ContentResponse response = httpClient.newRequest(ObserverProperties.getWalletUrl() + "/burst")
                    .param("requestType", "getAllOpenAskOrders")
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send();

            Orders askOrders = objectMapper.readValue(response.getContentAsString(), Orders.class);
            LOG.info("received '" + askOrders.getOpenOrders().size() + "' askOrders in '"
                    + askOrders.getRequestProcessingTime() + "' ms");

            addOrders(OrderType.ASK, orderLookup, askOrders, assetLookup);

            response = httpClient.newRequest(ObserverProperties.getWalletUrl() + "/burst")
                    .param("requestType", "getAllOpenBidOrders")
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send();

            Orders bidOrders = objectMapper.readValue(response.getContentAsString(), Orders.class);
            LOG.info("received '" + bidOrders.getOpenOrders().size() + "' bidOrders in '"
                    + bidOrders.getRequestProcessingTime() + "' ms");

            addOrders(OrderType.BID, orderLookup, bidOrders, assetLookup);
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getAllOpenAskOrders' and 'getAllOpenBidOrders': " + e.getMessage());
        }
        return orderLookup;
    }

    private void addOrders(OrderType orderType, Map<OrderType, Map<Asset, List<Order>>> orderLookup, Orders orders,
            Map<String, Asset> assetLookup) {
        Map<Asset, List<Order>> askOrderLookup = new HashMap<>();
        for (Order order : orders.getOpenOrders()) {
            Asset asset = assetLookup.get(order.getAsset());
            if (!askOrderLookup.containsKey(asset)) {
                askOrderLookup.put(asset, new ArrayList<>());
            }
            askOrderLookup.get(asset).add(order);
        }
        orderLookup.put(orderType, askOrderLookup);
    }

    // 3 possibilities to blacklist a asset
    // - issuer has blacklisted it (has to send assetId to provided account (from asset or issuer account))
    // - blacklisted in properties
    // - blacklisted via trusted community member accounts (from properties)
    private List<String> getBlacklistedAssets() {
        // todo get messages from blacklist account
        // todo filter valid messages
        // todo filter allowed messages by moderator / issuer
        // todo re-add whitelisted
        // todo return remaining assetIds

        return null;
    }

}