public interface HttpClient {

    public void setListener(HttpClientListener listener);

    public HttpClientListener getListener();

    /**
     * Returns the HTTP response code
     */
    public int head(String url, int connectTimeoutInMillis) throws IOException;

    public String get(String url) throws IOException;

    public String get(String url, int timeout) throws IOException;

    public String get(String url, int timeout, String userAgent) throws IOException;

    public String get(String url, int timeout, String userAgent, String referrer, String cookie) throws IOException;

    public String get(String url, int timeout, String userAgent, String referrer, String cookie, Map<String, String> customHeaders) throws IOException;

    public byte[] getBytes(String url, int timeout, String userAgent, String referrer);

    public byte[] getBytes(String url, int timeout, String referrer);

    public byte[] getBytes(String url, int timeout);

    public byte[] getBytes(String url);

    public void save(String url, File file) throws IOException;

    public void save(String url, File file, boolean resume) throws IOException;

    public void save(String url, File file, boolean resume, int timeout, String userAgent) throws IOException;

    public String post(String url, int timeout, String userAgent, Map<String, String> formData);

    public String post(String url, int timeout, String userAgent, String content, boolean gzip) throws IOException;

    public String post(String url, int timeout, String userAgent, String content, String postContentType, boolean gzip) throws IOException;

    public void cancel();

    public boolean isCanceled();

    public interface HttpClientListener {

        public void onError(HttpClient client, Throwable e);

        public void onData(HttpClient client, byte[] buffer, int offset, int length);

        public void onComplete(HttpClient client);

        public void onCancel(HttpClient client);

        public void onHeaders(HttpClient httpClient, Map<String, List<String>> headerFields);
    }

    public abstract class HttpClientListenerAdapter implements HttpClientListener {

        public void onError(HttpClient client, Throwable e) {
        }

        public void onData(HttpClient client, byte[] buffer, int offset, int length) {
        }

        public void onComplete(HttpClient client) {
        }

        public void onCancel(HttpClient client) {
        }

        public void onHeaders(HttpClient httpClient, Map<String, List<String>> headerFields) {
        }
    }

    public static class HttpRangeException extends IOException {

        private static final long serialVersionUID = 1891038288667531894L;

        public HttpRangeException(String message) {
            super(message);
        }
    }

    public static final class RangeNotSupportedException extends HttpRangeException {

        private static final long serialVersionUID = -3356618211960630147L;

        public RangeNotSupportedException(String message) {
            super(message);
        }
    }

    public static final class HttpRangeOutOfBoundsException extends HttpRangeException {

        private static final long serialVersionUID = -335661829606230147L;

        public HttpRangeOutOfBoundsException(int rangeStart, long expectedFileSize) {
            super("HttpRange Out of Bounds error: start=" + rangeStart + " expected file size=" + expectedFileSize);
        }

    }

    public static final class ResponseCodeNotSupportedException extends IOException {
        private final int responseCode;

        public ResponseCodeNotSupportedException(int code) {
            responseCode = code;
        }

        public int getResponseCode() {
            return responseCode;
        }
    }
} public final class JdkHttpClient implements HttpClient {

    private static final Logger LOG = Logger.getLogger(JdkHttpClient.class);

    private static final int DEFAULT_TIMEOUT = 10000;
    private static final String DEFAULT_USER_AGENT = "Wget 1.2";
    private HttpClientListener listener;

    private boolean canceled;

    @Override
    public int head(String url, int connectTimeoutInMillis) throws IOException {
        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        connection.setConnectTimeout(connectTimeoutInMillis);
        connection.setReadTimeout(connectTimeoutInMillis);
        connection.setRequestMethod("HEAD");
        return connection.getResponseCode();
    }


    public String get(String url) throws IOException {
        return get(url, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT);
    }

    public String get(String url, int timeout) throws IOException {
        return get(url, timeout, DEFAULT_USER_AGENT);
    }

    public String get(String url, int timeout, String userAgent) throws IOException {
        return get(url, timeout, userAgent, null, null);
    }

    public String get(String url, int timeout, String userAgent, String referrer, String cookie) throws IOException {
        return get(url, timeout, userAgent, referrer, cookie, null);
    }

    @Override
    public String get(String url, int timeout, String userAgent, String referrer, String cookie, Map<String, String> customHeaders) throws IOException {
        String result = null;

        ByteArrayOutputStream baos = null;

        try {
            baos = new ByteArrayOutputStream();
            get(url, baos, timeout, userAgent, referrer, cookie, -1, -1, customHeaders);

            result = new String(baos.toByteArray(), "UTF-8");
        } catch (java.net.SocketTimeoutException timeoutException) {
            throw timeoutException;
        } catch (IOException e) {
            throw e;
        } finally {
            closeQuietly(baos);
        }

        return result;
    }

    public byte[] getBytes(String url, int timeout, String userAgent, String referrer) {
        byte[] result = null;

        ByteArrayOutputStream baos = null;

        try {
            baos = new ByteArrayOutputStream();
            get(url, baos, timeout, userAgent, referrer, null, -1);

            result = baos.toByteArray();
        } catch (Throwable e) {
            LOG.error("Error getting bytes from http body response: " + e.getMessage(), e);
        } finally {
            closeQuietly(baos);
        }

        return result;
    }

    @Override
    public byte[] getBytes(String url, int timeout, String referrer) {
        return getBytes(url, timeout, DEFAULT_USER_AGENT, referrer);
    }

    @Override
    public byte[] getBytes(String url, int timeout) {
        return getBytes(url, timeout, null);
    }

    @Override
    public byte[] getBytes(String url) {
        return getBytes(url, DEFAULT_TIMEOUT);
    }

    public void save(String url, File file) throws IOException {
        save(url, file, false, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT);
    }

    public void save(String url, File file, boolean resume) throws IOException {
        save(url, file, resume, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT);
    }

    public void save(String url, File file, boolean resume, int timeout, String userAgent) throws IOException {
        save(url, file, resume, timeout, userAgent, null);
    }

    public void save(String url, File file, boolean resume, int timeout, String userAgent, String referrer) throws IOException {
        FileOutputStream fos = null;
        int rangeStart = 0;

        try {
            if (resume && file.exists()) {
                fos = new FileOutputStream(file, true);
                rangeStart = (int) file.length();
            } else {
                fos = new FileOutputStream(file, false);
                rangeStart = -1;
            }

            get(url, fos, timeout, userAgent, null, referrer, rangeStart);
        } finally {
            closeQuietly(fos);
        }
    }

    @Override
    public String post(String url, int timeout, String userAgent, String content, String postContentType, boolean gzip) throws IOException {
        String result = null;
        canceled = false;
        final URL u = new URL(url);
        final HttpURLConnection conn = (HttpURLConnection) u.openConnection();
        conn.setDoOutput(true);

        conn.setConnectTimeout(timeout);
        conn.setReadTimeout(timeout);
        conn.setRequestProperty("User-Agent", userAgent);
        conn.setInstanceFollowRedirects(false);

        if (conn instanceof HttpsURLConnection) {
            setHostnameVerifier((HttpsURLConnection) conn);
        }

        byte[] data = content.getBytes("UTF-8");

        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", postContentType);
        conn.setRequestProperty("charset", "utf-8");
        conn.setUseCaches(false);

        ByteArrayInputStream in = new ByteArrayInputStream(data);

        try {
            OutputStream out = null;
            if (gzip) {
                out = new GZIPOutputStream(conn.getOutputStream());
            } else {
                out = conn.getOutputStream();
            }

            byte[] b = new byte[4096];
            int n = 0;
            while (!canceled && (n = in.read(b, 0, b.length)) != -1) {
                if (!canceled) {
                    out.write(b, 0, n);
                    out.flush();
                    onData(b, 0, n);
                }
            }

            closeQuietly(out);

            conn.connect();
            int httpResponseCode = getResponseCode(conn);

            if (httpResponseCode != HttpURLConnection.HTTP_OK && httpResponseCode != HttpURLConnection.HTTP_PARTIAL) {
                throw new ResponseCodeNotSupportedException(httpResponseCode);
            }

            if (canceled) {
                onCancel();
            } else {
//                BufferedInputStream bis = new BufferedInputStream(conn.getInputStream(), 4096);
//                ByteArrayBuffer baf = new ByteArrayBuffer(1024);
//                byte[] buffer = new byte[64];
//                int read = 0;
//                while (true) {
//                    read = bis.read(buffer);
//                    if (read == -1) {
//                        break;
//                    }
//                    baf.append(buffer, 0, read);
//                }
//                result = new String(baf.toByteArray());
//                onComplete();
            }
        } catch (Exception e) {
            onError(e);
        } finally {
            closeQuietly(in);
            closeQuietly(conn);
        }
        return result;
    }


    @Override
    public String post(String url, int timeout, String userAgent, String content, boolean gzip) throws IOException {
        return post(url, timeout, userAgent, content, "text/plain", gzip);
    }

    /**
     * Post a form Content-type: application/x-www-form-urlencoded
     */
    @Override
    public String post(String url, int timeout, String userAgent, Map<String, String> formData) {
        String result = null;

        ByteArrayOutputStream baos = null;

        try {
            baos = new ByteArrayOutputStream();
            post(url, baos, timeout, userAgent, formData);
            result = new String(baos.toByteArray(), "UTF-8");
        } catch (Throwable e) {
            LOG.error("Error posting data via http: " + e.getMessage(), e);
        } finally {
            closeQuietly(baos);
        }

        return result;
    }

    private String buildRange(int rangeStart, int rangeLength) {
        String prefix = "bytes=" + rangeStart + "-";
        return prefix + ((rangeLength > -1) ? (rangeStart + rangeLength) : "");
    }

    private void get(String url, OutputStream out, int timeout, String userAgent, String referrer, String cookie, int rangeStart) throws IOException {
        get(url, out, timeout, userAgent, referrer, cookie, rangeStart, -1, null);
    }

    private void get(String url, OutputStream out, int timeout, String userAgent, String referrer, String cookie, int rangeStart, int rangeLength, final Map<String, String> customHeaders) throws IOException {
        canceled = false;
        final URL u = new URL(url);
        final URLConnection conn = u.openConnection();

        conn.setConnectTimeout(timeout);
        conn.setReadTimeout(timeout);
        conn.setRequestProperty("User-Agent", userAgent);

        if (referrer != null) {
            conn.setRequestProperty("Referer", referrer);
        }

        if (cookie != null) {
            conn.setRequestProperty("Cookie", cookie);
        }

        if (conn instanceof HttpURLConnection) {
            ((HttpURLConnection) conn).setInstanceFollowRedirects(true);
        }

        if (conn instanceof HttpsURLConnection) {
            setHostnameVerifier((HttpsURLConnection) conn);
        }

        if (rangeStart > 0) {
            conn.setRequestProperty("Range", buildRange(rangeStart, rangeLength));
        }

        if (customHeaders != null && customHeaders.size() > 0) {
            //put down here so it can overwrite any of the previous headers.
            setCustomHeaders(conn, customHeaders);
        }

        InputStream in = conn.getInputStream();
        if ("gzip".equals(conn.getContentEncoding())) {
            in = new GZIPInputStream(in);
        }

        int httpResponseCode = getResponseCode(conn);

        if (httpResponseCode != HttpURLConnection.HTTP_OK &&
                httpResponseCode != HttpURLConnection.HTTP_PARTIAL &&
                httpResponseCode != HttpURLConnection.HTTP_MOVED_TEMP &&
                httpResponseCode != HttpURLConnection.HTTP_MOVED_PERM) {
            throw new ResponseCodeNotSupportedException(httpResponseCode);
        }

        onHeaders(conn.getHeaderFields());
        checkRangeSupport(rangeStart, conn);

        try {
            byte[] b = new byte[4096];
            int n = 0;
            while (!canceled && (n = in.read(b, 0, b.length)) != -1) {
                if (!canceled) {
                    out.write(b, 0, n);
                    onData(b, 0, n);
                }
            }

            closeQuietly(out);

            if (canceled) {
                onCancel();
            } else {
                onComplete();
            }
        } catch (Exception e) {
            onError(e);
        } finally {
            closeQuietly(in);
            closeQuietly(conn);
        }
    }




    private void post(String url, OutputStream out, int timeout, String userAgent, Map<String, String> formData) throws IOException {
        canceled = false;
        final URL u = new URL(url);
        final HttpURLConnection conn = (HttpURLConnection) u.openConnection();
        conn.setDoOutput(true);

        conn.setConnectTimeout(timeout);
        conn.setReadTimeout(timeout);
        conn.setRequestProperty("User-Agent", userAgent);
        conn.setInstanceFollowRedirects(false);

        if (conn instanceof HttpsURLConnection) {
            setHostnameVerifier((HttpsURLConnection) conn);
        }

        StringBuilder sb = new StringBuilder();
        if (formData != null && formData.size() > 0) {
            for (Entry<String, String> kv : formData.entrySet()) {
                sb.append("&");
                sb.append(kv.getKey());
                sb.append("=");
                sb.append(kv.getValue());
            }
            sb.deleteCharAt(0);
        }

        byte[] data = sb.toString().getBytes("UTF-8");

        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setRequestProperty("charset", "utf-8");
        conn.setUseCaches(false);

        InputStream in = new ByteArrayInputStream(data);

        try {
            OutputStream postOut = conn.getOutputStream();

            byte[] b = new byte[4096];
            int n = 0;
            while (!canceled && (n = in.read(b, 0, b.length)) != -1) {
                if (!canceled) {
                    postOut.write(b, 0, n);
                    postOut.flush();
                    onData(b, 0, n);
                }
            }

            closeQuietly(postOut);
            closeQuietly(in);

            conn.connect();

            in = conn.getInputStream();
            int httpResponseCode = getResponseCode(conn);

            if (httpResponseCode != HttpURLConnection.HTTP_OK && httpResponseCode != HttpURLConnection.HTTP_PARTIAL) {
                throw new ResponseCodeNotSupportedException(httpResponseCode);
            }

            b = new byte[4096];
            n = 0;
            while (!canceled && (n = in.read(b, 0, b.length)) != -1) {
                if (!canceled) {
                    out.write(b, 0, n);
                    onData(b, 0, n);
                }
            }

            closeQuietly(out);

            if (canceled) {
                onCancel();
            } else {
                onComplete();
            }
        } catch (Exception e) {
            onError(e);
        } finally {
            closeQuietly(in);
            closeQuietly(conn);
        }
    }

    private void setCustomHeaders(URLConnection conn, Map<String, String> headers) {
        for (Entry<String, String> e : headers.entrySet()) {
            conn.setRequestProperty(e.getKey(), e.getValue());
        }
    }

    private void setHostnameVerifier(HttpsURLConnection conn) {
        conn.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
    }

    private int getResponseCode(URLConnection conn) {
        try {
            return ((HttpURLConnection) conn).getResponseCode();
        } catch (Throwable e) {
            e.printStackTrace();
            LOG.error("can't get response code ", e);
            return -1;
        }
    }

    private void checkRangeSupport(int rangeStart, URLConnection conn) throws HttpRangeOutOfBoundsException, RangeNotSupportedException {

        boolean hasContentRange = conn.getHeaderField("Content-Range") != null;
        boolean hasAcceptRanges = conn.getHeaderField("Accept-Ranges") != null && conn.getHeaderField("Accept-Ranges").equals("bytes");

        if (rangeStart > 0 && !hasContentRange && !hasAcceptRanges) {
            RangeNotSupportedException rangeNotSupportedException = new RangeNotSupportedException("Server does not support bytes range request");
            onError(rangeNotSupportedException);
            throw rangeNotSupportedException;
        }
    }

    private void onHeaders(Map<String, List<String>> headerFields) {
        if (getListener() != null) {
            try {
                getListener().onHeaders(this, headerFields);
            } catch (Exception e) {
                LOG.warn(e.getMessage(), e);
            }
        }
    }

    private void onCancel() {
        if (getListener() != null) {
            try {
                getListener().onCancel(this);
            } catch (Exception e) {
                LOG.warn(e.getMessage(), e);
            }
        }
    }

    private void onData(byte[] b, int i, int n) {
        if (getListener() != null) {
            try {
                getListener().onData(this, b, 0, n);
            } catch (Exception e) {
                LOG.warn(e.getMessage(), e);
            }
        }
    }

    protected void onError(Exception e) {
        if (getListener() != null) {
            try {
                getListener().onError(this, e);
            } catch (Exception e2) {
                LOG.warn(e2.getMessage());
            }
        }  else {
            e.printStackTrace();
        }
    }

    protected void onComplete() {
        if (getListener() != null) {
            try {
                getListener().onComplete(this);
            } catch (Exception e) {
                LOG.warn(e.getMessage(), e);
            }
        }
    }

    private static void closeQuietly(Closeable closeable) {
        try {
            if (closeable != null) {
                closeable.close();
            }
        } catch (IOException ioe) {
            // ignore
        }
    }

    private void closeQuietly(URLConnection conn) {
        if (conn instanceof HttpURLConnection) {
            try {
                ((HttpURLConnection) conn).disconnect();
            } catch (Throwable e) {
                LOG.debug("Error closing http connection", e);
            }
        }
    }

    @Override
    public void setListener(HttpClientListener listener) {
        this.listener = listener;
    }

    @Override
    public HttpClientListener getListener() {
        return listener;
    }

    @Override
    public void cancel() {
        canceled = true;
    }

    @Override
    public boolean isCanceled() {
        return canceled;
    }
} public final class Configuration {

    private static final Logger LOG = Lumberjack.getLogger(Configuration.class);

    private static final String ST_CONF_ENV = "ST_CONF";

    private final Properties properties;

    public Configuration(String filename) {
        properties = new Properties();

        try {
            properties.load(new FileInputStream(filename));
        } catch (Throwable e) {
            throw new RuntimeException("Can't run without configuration", e);
        }
    }

    public Configuration() {
        this(System.getenv(ST_CONF_ENV));
    }

    public String getString(String key) {
        String r = "";
        
        try{
            r = properties.getProperty(key);
        } catch (Throwable e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }
        
        return r;
    }

    public int getInt(String key) {
        int r = 0;

        try {
            r = Integer.parseInt(properties.getProperty(key));
        } catch (Throwable e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }

        return r;
    }
    
    public float getFloat(String key) {
        float r = 0f;

        try {
            r = Float.parseFloat(properties.getProperty(key));
        } catch (Throwable e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }

        return r;
    }

    public boolean getBoolean(String key) {
        boolean r = false;

        try {
            r = Boolean.parseBoolean(properties.getProperty(key));
        } catch (Throwable e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }

        return r;
    }

    public List<String> getList(String key) {

        List<String> results = new ArrayList<>();
        String property = null;
        String[] split = null;

        try {
            property = properties.getProperty(key);
            
            if (property == null) {
                return null;
            }
            
            split = property.split(",");
            results = Arrays.asList(split);
        } catch (Exception e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }

        return results;
    }

    public long getLong(String key) {
        long r = 0;

        try {
            r = Long.parseLong(properties.getProperty(key));
        } catch (Throwable e) {
            LOG.log(Level.SEVERE, String.format("Error reading configuration key %s", key), e);
        }

        return r;
    }
} public class ConfigurationKeys {
    public static final String ST_VERSION = "st.version";
    public static final String ST_SERVER_PORT = "st.server_port";
    public static final String ST_SITE_NAME = "st.site_name";
    public static final String ST_TEMPLATES_FOLDER = "st.templates.folder";
    public static final String ST_SITE_SLOGAN = "st.site_slogan";
    public static final String ST_SITE_DESCRIPTION = "st.site_description";
    public static final String ST_SITE_KEYWORDS = "st.site_keywords";
    public static final String ST_SHIPPING_FROM = "st.shipping_from";
    public static final String ST_HOSTNAME = "st.hostname";
    public static final String ST_SERVER_IP = "st.server_ip";
    public static final String ST_IS_LOBBY_SERVER = "st.is_lobby_server";
    public static final String ST_USE_LAN_MAPPINGS= "st.use_lan_mappings";
    public static final String ST_SEND_ACCESS_CONTROL_ALLOW_ORIGIN = "st.send_access_control_allow_origin";
    public static final String ST_SEARCH_HOPS = "st.search.hops";
    public static final String ST_PRODUCTS_SET = "st.products_set";
    public static final String ST_QUERY_NODE_TIMEOUT_MS = "st.query_node_timeout_ms";

    protected ConfigurationKeys() {
    }
} public class Product {

    public long id;
    public String name;
    public String description;
    public String[] keywords;
    public String thumbnailUrl;

    /**
     * No price volatility anxiety, thank you BitPay.
     */
    public float usdPrice;
    public String bitpayData;

    // TODO: list of pictures urls, for more product pictures.
} public class SearchResult {

    public Product product;
    public Store store;

    public String getName() {
        return product.name;
    }

    public String getStore() {
        return store.name;
    }

    public String getUrl() {
        // not including the port. the web server proxying the request to SellerTrade should know it.
        String hostname = (store.hostname != null) ?  store.hostname : store.address;
        return "http://" + hostname + "/product/?id=" + product.id;
    }

    public float getPrice() {
        return product.usdPrice;
    }

    public String getTrimmableDescription() {
        String description = product.description;
        if (product.description.length() > 160) {
            description = product.description.substring(0,159) + "...";
        }
        return description;
    }
} public class SearchResultList {

    public List<SearchResult> results;
} public class Store {
    public String hostname; /** domain name */
    public String address; /** ip address */
    public int port;
    public String name;
} public class DHTNode {
    public final String ipAddress;
    public final int port;

    public DHTNode(String ipAddress, int port) {
        this.ipAddress = ipAddress;
        this.port = port;
    }

    public final String getIPAddress() {
        return ipAddress;
    }

    /** Returns the http port under which the peer is being announced.
     * All further HTTP interaction between servers should be done through this
     * port*/
    public final int getHttpPort() {
        return port;
    }
} public interface DHTService {

    public List<DHTNode> getNodes();

    public List<DHTNode> getNodes(String keyword);

    public void announceNode(int httpPort);

    public void announceKeywords(List<String> keywords, int httpPort);
} public final class JsonUtils {

    private static final Gson gson = new GsonBuilder().create();

    private JsonUtils() {
    }

    /**
     * This method serializes the specified object into its equivalent Json
     * representation.
     * 
     * This method should only be used when the specified object is not a generic type.
     * 
     * @param obj the object for which Json representation is to be created
     * @return Json representation of obj
     */
    public static String toJson(Object obj) {
        return gson.toJson(obj);
    }

    /**
     * This method deserializes the specified Json into an object of the specified class.
     * 
     * This method should not be used if the desired type is a generic type.
     * 
     * @param <T> the type of the desired object
     * @param json the string from which the object is to be deserialized
     * @param classOfT the class of T
     * @return an object of type T from the string
     */
    public static <T> T toObject(String json, Class<T> classOfT) {
        return gson.fromJson(json, classOfT);
    }
}
import com.seller.trade.models.Product;
import com.seller.trade.models.SearchResult;
import com.seller.trade.models.SearchResultList;
import com.seller.trade.models.Store;
import com.seller.trade.services.dht.DHTNode;
import com.seller.trade.services.dht.DHTService;
import com.seller.trade.utils.JsonUtils;
import java.util.*;
import com.frostwire.util.HttpClient;
import com.frostwire.util.JdkHttpClient;
import com.seller.trade.core.Configuration;
import com.seller.trade.core.ConfigurationKeys;
/**
 * The MIT License
 ===============

 Copyright (C) 2015 SellerTrade Developers

 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 com.seller.trade.services;



public final class StoreService {

    private final Configuration configuration;
    private final DHTService dhtService;