burstcoin.observer.service.ATService.java Source code

Java tutorial

Introduction

Here is the source code for burstcoin.observer.service.ATService.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.CrowdfundBean;
import burstcoin.observer.bean.CrowdfundState;
import burstcoin.observer.controller.CrowdfundController;
import burstcoin.observer.event.CrowdfundUpdateEvent;
import burstcoin.observer.service.model.State;
import burstcoin.observer.service.model.at.AutomatedTransaction;
import burstcoin.observer.service.model.at.AutomatedTransactionIds;
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.ApplicationContext;
import org.springframework.stereotype.Component;

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

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

    private static final String CROWDFUND_AT_CODE = "350003000000002501000000350004020000002102000000030000004f350103040000003304030400000035250105000000"
            + "1b050000004a3506030600000035070304000000320a0301070000000200000000000000330204060000001a240000000"
            + "1070000000100000000000000320b033203043502030400000033040304000000352501050000001b05000000f23507030"
            + "4000000320b033203041a7c";

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private HttpClient httpClient;

    @Autowired
    private ApplicationContext context;

    private Timer timer = new Timer();

    private List<String> blacklistedAtRS;

    @PostConstruct
    public void init() {
        blacklistedAtRS = new ArrayList<>();
        blacklistedAtRS.add("BURST-VGHL-Z8N5-SMJB-4GJNN");
        blacklistedAtRS.add("BURST-Q9E6-4BVM-GXPU-BMSSU");
        blacklistedAtRS.add("BURST-S59U-5GQ5-ZGVH-7T8X3");
        blacklistedAtRS.add("BURST-8QLW-LULL-HUC9-FYVQX");
        blacklistedAtRS.add("BURST-VBF4-SBVZ-M4FU-74DQQ");
        blacklistedAtRS.add("BURST-PZGK-23GF-F3SB-4R44P");
        blacklistedAtRS.add("BURST-653T-WXLE-WMGK-9NN69");
        blacklistedAtRS.add("BURST-5LRG-YCVL-Z9LL-4C2BX");
        blacklistedAtRS.add("BURST-LT2E-ZKB7-J48V-8HPGG");

        LOG.info("Started repeating 'check at' task.");
        startCheckAutomatedTransactionsTask();
    }

    private void startCheckAutomatedTransactionsTask() {
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    LOG.info("START import at data");
                    Map<String, AutomatedTransaction> atLookup = new HashMap<>();
                    for (String atId : getAutomatedTransactionIds()) {
                        AutomatedTransaction automatedTransaction = getAutomatedTransaction(atId);
                        atLookup.put(atId, automatedTransaction);
                    }
                    State state = getState();

                    LOG.info("FINISH import at data!");
                    Long currentBlock = state.getNumberOfBlocks();

                    List<CrowdfundBean> crowdfundBeans = new ArrayList<>();

                    // different by code?!
                    Map<String, List<AutomatedTransaction>> atByCodeLookup = new HashMap<>();
                    for (AutomatedTransaction automatedTransaction : atLookup.values()) {
                        if (!atByCodeLookup.containsKey(automatedTransaction.getMachineCode())) {
                            atByCodeLookup.put(automatedTransaction.getMachineCode(), new ArrayList<>());
                        }
                        atByCodeLookup.get(automatedTransaction.getMachineCode()).add(automatedTransaction);
                    }

                    for (Map.Entry<String, List<AutomatedTransaction>> entry : atByCodeLookup.entrySet()) {
                        // filter for crowdfund
                        if (entry.getKey().contains(CROWDFUND_AT_CODE)) {
                            for (AutomatedTransaction at : entry.getValue()) {
                                // target amount
                                String targetAmountHex = at.getMachineData().substring(48, 64);
                                String targetAmount = getATLong(targetAmountHex);
                                String targetAmountInt = targetAmount.length() > 8
                                        ? targetAmount.substring(0, targetAmount.length() - 8)
                                        : targetAmount;

                                String decisionHex = at.getMachineData().substring(16, 32);
                                Integer decision = Integer.valueOf(getATLong(decisionHex));

                                String transactionHex = at.getMachineData().substring(8, 16);
                                Integer transaction = Integer.valueOf(getATLong(transactionHex));

                                Long ends = transaction + decision - currentBlock;
                                Long running = currentBlock - transaction;

                                String fundedHex = at.getMachineData().substring(7 * 16, 7 * 16 + 16);
                                String funded = getATLong(fundedHex);
                                CrowdfundState cfState = CrowdfundState.ACTIVE;
                                switch (funded) {
                                case "2":
                                    cfState = CrowdfundState.NOT_FUNDED;
                                    break;
                                case "1":
                                    cfState = CrowdfundState.FUNDED;
                                    break;
                                }

                                // skip active that never got started/funded while running
                                if ((ends > 0 || !CrowdfundState.ACTIVE.equals(cfState))
                                        && !blacklistedAtRS.contains(at.getAtRS())) {

                                    String current = "0";
                                    if (at.getBalanceNQT().length() > 8) {
                                        current = at.getBalanceNQT().substring(0, at.getBalanceNQT().length() - 8);
                                    }
                                    if (CrowdfundState.FUNDED.equals(cfState)
                                            || CrowdfundState.NOT_FUNDED.equals(cfState)) {
                                        String gatheredAmountHex = at.getMachineData().substring(32, 48);
                                        String gatheredAmount = getATLong(gatheredAmountHex);
                                        if (gatheredAmount.length() > 8) {
                                            current = gatheredAmount.substring(0, gatheredAmount.length() - 8);
                                        }
                                    }

                                    double percent = getPercentageCorrect(Integer.valueOf(targetAmountInt),
                                            Integer.valueOf(current));
                                    int round = Math.round((float) percent);
                                    round = round > 100 ? 100 : round;

                                    crowdfundBeans.add(new CrowdfundBean(at.getAt(), at.getAtRS(),
                                            at.getCreatorRS(), at.getName(),
                                            at.getDescription().length() > 9 ? at.getDescription().substring(0,
                                                    at.getDescription().length() - 9) : at.getDescription(),
                                            cfState, targetAmountInt, current, round + "", percent,
                                            ends.equals(currentBlock) ? "N/A" : String.valueOf(ends)));
                                }
                            }

                            Collections.sort(crowdfundBeans, new Comparator<CrowdfundBean>() {
                                @Override
                                public int compare(CrowdfundBean o1, CrowdfundBean o2) {
                                    return Long.valueOf(o2.getCurrentAmount())
                                            .compareTo(Long.valueOf(o1.getCurrentAmount()));
                                }
                            });
                            Collections.sort(crowdfundBeans, new Comparator<CrowdfundBean>() {
                                @Override
                                public int compare(CrowdfundBean o1, CrowdfundBean o2) {
                                    return o1.getState().compareTo(o2.getState());
                                }
                            });

                            // todo no idea why this currently does not work
                            // context.publishEvent(new CrowdfundUpdateEvent(crowdfundBeans));
                            // workaround:
                            context.getBean(CrowdfundController.class)
                                    .handleMessage(new CrowdfundUpdateEvent(crowdfundBeans));
                        }
                    }
                } catch (Exception e) {
                    LOG.error("Failed update crowdfund data");
                }
            }
        }, 200, ObserverProperties.getCrowdfundRefreshInterval());
    }

    private List<String> getAutomatedTransactionIds() {
        List<String> atIds = null;
        try {
            ContentResponse response = httpClient
                    .newRequest(ObserverProperties.getWalletUrl() + "/burst?requestType=getATIds")
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send();

            AutomatedTransactionIds automatedTransactionIds = objectMapper.readValue(response.getContentAsString(),
                    AutomatedTransactionIds.class);
            //      LOG.info("received '" + automatedTransactionIds.getAtIds().size() + "' at's in '" + automatedTransactionIds.getRequestProcessingTime() + "' ms");

            atIds = automatedTransactionIds.getAtIds();
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getATIds': " + e.getMessage());
        }
        return atIds;
    }

    private AutomatedTransaction getAutomatedTransaction(String atId) {
        AutomatedTransaction automatedTransaction = null;
        try {
            ContentResponse response = httpClient.POST(ObserverProperties.getWalletUrl() + "/burst")
                    .param("requestType", "getAT").param("at", atId)
                    .timeout(ObserverProperties.getConnectionTimeout(), TimeUnit.MILLISECONDS).send();

            automatedTransaction = objectMapper.readValue(response.getContentAsString(),
                    AutomatedTransaction.class);
            //      LOG.info("received '" + automatedTransaction.getName() + "' ats in '" + automatedTransaction.getRequestProcessingTime() + "' ms");
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getAT': " + e.getMessage());
        }
        return automatedTransaction;
    }

    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);

            // Look at the response
            if (response.getStatus() == 200) {
                // Use try-with-resources to close input stream.
                try (InputStream responseContent = listener.getInputStream()) {
                    state = objectMapper.readValue(responseContent, State.class);
                } catch (Exception e) {
                    LOG.error("Failed to receive state.");
                }
            }
        } catch (Exception e) {
            LOG.warn("Error: Failed to 'getState': " + e.getMessage());
        }
        return state;
    }

    public static float getPercentageCorrect(int questions, int correct) {
        float proportionCorrect = ((float) correct) / ((float) questions);
        return proportionCorrect * 100;
    }

    public static String getATLong(String hex) {
        ByteBuffer bf = ByteBuffer.allocate(8);
        bf.order(ByteOrder.LITTLE_ENDIAN);
        bf.put(parseHexString(hex));
        return toUnsignedLong(bf.getLong(0));
    }

    //
    // nxt.util.Convert methods
    //

    public static final BigInteger two64 = new BigInteger("18446744073709551616");

    public static byte[] parseHexString(String hex) {
        byte[] bytes = new byte[hex.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            int char1 = hex.charAt(i * 2);
            char1 = char1 > 0x60 ? char1 - 0x57 : char1 - 0x30;
            int char2 = hex.charAt(i * 2 + 1);
            char2 = char2 > 0x60 ? char2 - 0x57 : char2 - 0x30;
            if (char1 < 0 || char2 < 0 || char1 > 15 || char2 > 15) {
                throw new NumberFormatException("Invalid hex number: " + hex);
            }
            bytes[i] = (byte) ((char1 << 4) + char2);
        }
        return bytes;
    }

    public static String toUnsignedLong(long objectId) {
        if (objectId >= 0) {
            return String.valueOf(objectId);
        }
        BigInteger id = BigInteger.valueOf(objectId).add(two64);
        return id.toString();
    }
}