com.dawg6.d3api.server.D3IO.java Source code

Java tutorial

Introduction

Here is the source code for com.dawg6.d3api.server.D3IO.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2015 Scott Clarke (scott@dawg6.com).
 *
 * This file is part of Dawg6's battle.net Diablo 3 API.
 *
 * Dawg6's Demon Hunter DPS Calculator is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Dawg6's Demon Hunter DPS Calculator is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package com.dawg6.d3api.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;

import com.dawg6.d3api.server.oauth.Account;
import com.dawg6.d3api.server.oauth.Token;
import com.dawg6.d3api.shared.CareerProfile;
import com.dawg6.d3api.shared.HeroProfile;
import com.dawg6.d3api.shared.ItemInformation;
import com.dawg6.d3api.shared.Leaderboard;
import com.dawg6.d3api.shared.Realm;
import com.dawg6.d3api.shared.Season;
import com.dawg6.d3api.shared.SeasonIndex;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class D3IO {

    protected static final Logger log = Logger.getLogger(D3IO.class.getName());

    public static final Integer DEFAULT_MAX_REQUESTS_PER_SECOND = 85;
    public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
    public static final int DEFAULT_READ_TIMEOUT = 30000;
    public static final String TOKEN_SERVER_URL = "https://us.battle.net/oauth/token";
    public static final String ACCOUNT_API_URL = "https://us.api.battle.net/account/user";
    public static final Realm DEFAULT_ITEM_REALM = Realm.US;
    public static final int DEFAULT_TIMEOUT_RETRIES = 10;

    protected int connectTimeout = DEFAULT_CONNECT_TIMEOUT;
    protected Token token = null;
    protected int readTimeout = DEFAULT_READ_TIMEOUT;
    protected long token_expires = 0L;
    protected int timeoutRetries = DEFAULT_TIMEOUT_RETRIES;
    protected Realm itemRealm = DEFAULT_ITEM_REALM;
    protected int maxRequests = DEFAULT_MAX_REQUESTS_PER_SECOND;
    protected String apiKey = null;
    protected String apiSecret = null;

    protected final List<Long> requests = new LinkedList<Long>();
    protected long numRequests = 0;

    protected final Cache<String, ItemInformation> itemCache = new Cache<String, ItemInformation>(true);

    protected long cacheHit = 0;
    protected long cacheMiss = 0;
    protected long errors = 0;
    protected long retryAttempts = 0;

    protected D3IO() {

    }

    public void throttle() {

        synchronized (requests) {

            if (requests.size() >= maxRequests) {
                long last = 0;

                if (requests.size() > 0)
                    last = requests.get(0);

                long now = System.currentTimeMillis();
                long delta = now - last;

                if (delta < 1000) {
                    // log.info("Throttling: " + (1000 - delta));
                    try {
                        Thread.sleep(1000 - delta);
                    } catch (Exception ie) {
                    }
                }

                while (requests.size() >= maxRequests)
                    requests.remove(0);
            }

            long finish = System.currentTimeMillis();

            requests.add(finish);
            numRequests++;
        }
    }

    protected synchronized String getApiKey() {

        return "&apikey=" + apiKey;
    }

    public long getCacheHits() {
        synchronized (itemCache) {
            return cacheHit;
        }
    }

    public long getCacheMisses() {
        synchronized (itemCache) {
            return cacheMiss;
        }
    }

    public void clearItemCache() {
        synchronized (itemCache) {
            itemCache.clear();
            cacheHit = 0;
            cacheMiss = 0;
        }
    }

    protected synchronized Token getToken() {

        if ((this.token == null) || (this.token_expires < System.currentTimeMillis())) {
            try {
                this.token = requestToken();
                this.token_expires = System.currentTimeMillis() + ((token.expires_in - (24L * 60L * 60L)) * 1000L);
            } catch (RuntimeException e) {
                log.log(Level.SEVERE, "Exception", e);
                throw e;
            } catch (Exception e2) {
                log.log(Level.SEVERE, "Exception", e2);
                throw new RuntimeException(e2);
            }
        }

        return token;
    }

    public String getAccessToken() {
        return "?access_token=" + getToken().access_token;
    }

    protected Token requestToken() throws Exception {

        CloseableHttpClient client = HttpClientBuilder.create().build();

        List<NameValuePair> params = new Vector<NameValuePair>();
        params.add(new BasicNameValuePair("client_id", apiKey));
        params.add(new BasicNameValuePair("client_secret", apiSecret));
        params.add(new BasicNameValuePair("grant_type", "client_credentials"));

        HttpPost request = new HttpPost(TOKEN_SERVER_URL);
        request.setEntity(new UrlEncodedFormEntity(params));

        HttpResponse response = client.execute(request);

        if (response.getStatusLine().getStatusCode() != 200) {
            log.log(Level.SEVERE, "HTTP Server Response: " + response.getStatusLine().getStatusCode());
            throw new RuntimeException("HTTP Server Response: " + response.getStatusLine().getStatusCode());
        }

        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }

        client.close();

        ObjectMapper mapper = new ObjectMapper();
        mapper = mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        Token token = mapper.readValue(result.toString(), Token.class);

        if ((token != null) && (token.error != null))
            throw new RuntimeException(token.error_description);

        return token;
    }

    protected Token requestToken(String code, String redirectUrl, String scope) throws Exception {

        CloseableHttpClient client = HttpClientBuilder.create().build();

        List<NameValuePair> params = new Vector<NameValuePair>();
        params.add(new BasicNameValuePair("client_id", apiKey));
        params.add(new BasicNameValuePair("client_secret", apiSecret));
        params.add(new BasicNameValuePair("grant_type", "authorization_code"));
        params.add(new BasicNameValuePair("redirect_uri", redirectUrl));
        params.add(new BasicNameValuePair("scope", scope));
        params.add(new BasicNameValuePair("code", code));

        HttpPost request = new HttpPost(TOKEN_SERVER_URL);
        request.setEntity(new UrlEncodedFormEntity(params));

        HttpResponse response = client.execute(request);

        if (response.getStatusLine().getStatusCode() != 200) {
            log.log(Level.SEVERE, "HTTP Server Response: " + response.getStatusLine().getStatusCode());
            throw new RuntimeException("HTTP Server Response: " + response.getStatusLine().getStatusCode());
        }

        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }

        client.close();

        ObjectMapper mapper = new ObjectMapper();
        mapper = mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        Token token = mapper.readValue(result.toString(), Token.class);

        if ((token != null) && (token.error != null))
            throw new RuntimeException(token.error_description);

        return token;
    }

    public Account getAccount(Token token) throws Exception {
        CloseableHttpClient client = HttpClientBuilder.create().build();

        URIBuilder builder = new URIBuilder(ACCOUNT_API_URL);
        builder.addParameter("access_token", token.access_token);

        //      log.info("Request = " + builder.toString());
        HttpGet request = new HttpGet(builder.toString());

        HttpResponse response = client.execute(request);

        if (response.getStatusLine().getStatusCode() != 200) {
            log.log(Level.SEVERE, "HTTP Server Response: " + response.getStatusLine().getStatusCode());
            throw new RuntimeException("HTTP Server Response: " + response.getStatusLine().getStatusCode());
        }

        BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        StringBuffer result = new StringBuffer();
        String line = "";
        while ((line = rd.readLine()) != null) {
            result.append(line);
        }

        client.close();

        ObjectMapper mapper = new ObjectMapper();
        mapper = mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        Account account = mapper.readValue(result.toString(), Account.class);

        if ((account != null) && (account.error != null))
            throw new RuntimeException(token.error_description);

        return account;
    }

    public CareerProfile readCareerProfile(String string)
            throws JsonParseException, JsonMappingException, IOException {
        return readCareerProfile(string, true);
    }

    public HeroProfile readHeroProfile(String string) throws JsonParseException, JsonMappingException, IOException {
        return readHeroProfile(string, true);
    }

    public ItemInformation readItemInformation(String string)
            throws JsonParseException, JsonMappingException, IOException {
        return readItemInformation(string, true);
    }

    public CareerProfile readCareerProfile(String string, boolean ignoreUnknown)
            throws JsonParseException, JsonMappingException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        if (ignoreUnknown) {
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }
        CareerProfile out = mapper.readValue(new File(string), CareerProfile.class);
        return out;
    }

    public HeroProfile readHeroProfile(String string, boolean ignoreUnknown)
            throws JsonParseException, JsonMappingException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        if (ignoreUnknown) {
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }
        HeroProfile out = mapper.readValue(new File(string), HeroProfile.class);
        return out;
    }

    public ItemInformation readItemInformation(String string, boolean ignoreUnknown)
            throws JsonParseException, JsonMappingException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        if (ignoreUnknown) {
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }
        ItemInformation out = mapper.readValue(new File(string), ItemInformation.class);
        return out;
    }

    public CareerProfile readCareerProfile(Realm realm, String name, int code)
            throws JsonParseException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        throttle();
        URL url = new URL(UrlHelper.careerProfileUrl(realm.getApiHost(), name, code) + getApiKey());

        CareerProfile out = readValue(mapper, url, CareerProfile.class);
        return out;
    }

    private <T> T readValue(ObjectMapper mapper, URL url, Class<T> clazz)
            throws JsonParseException, JsonMappingException, IOException {
        return readValue(mapper, url, clazz, this.timeoutRetries);
    }

    private <T> T readValue(ObjectMapper mapper, URL url, Class<T> clazz, int retries)
            throws JsonParseException, JsonMappingException, IOException {

        //      log.info("URL " + url);

        HttpURLConnection c = (HttpURLConnection) url.openConnection();
        c.setRequestMethod("GET");
        c.setRequestProperty("Content-length", "0");
        c.setUseCaches(false);
        c.setAllowUserInteraction(false);
        c.setConnectTimeout(connectTimeout);
        c.setReadTimeout(readTimeout);
        c.connect();
        int status = c.getResponseCode();

        switch (status) {
        case 200:
        case 201:
            BufferedReader br = new BufferedReader(new InputStreamReader(c.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line + "\n");
            }
            br.close();

            try {
                mapper = mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

                return mapper.readValue(sb.toString(), clazz);
            } catch (Exception e) {
                log.severe("JSON = " + sb.toString());
                log.log(Level.SEVERE, e.getMessage());

                return null;
            }

        case 408:
        case 504:
            log.info("HTTP Response: " + status + ", retries = " + retries + ", URL: " + url);
            errors++;
            onError(status);

            if (retries > 0) {
                retryAttempts++;
                onRetry();
                return readValue(mapper, url, clazz, retries - 1);
            } else
                return null;

        default:
            log.severe("HTTP Response: " + status + ", URL: " + url);
            return null;
        }
    }

    private void onRetry() {

    }

    private void onError(int status) {

    }

    public HeroProfile readHeroProfile(Realm realm, String name, int code, int id)
            throws JsonParseException, IOException {
        ObjectMapper mapper = new ObjectMapper();
        throttle();
        HeroProfile out = readValue(mapper,
                new URL(UrlHelper.heroProfileUrl(realm.getApiHost(), name, code, id) + getApiKey()),
                HeroProfile.class);
        return out;
    }

    public ItemInformation readItemInformation(Realm realm, String tooltipParams)
            throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.itemInformationUrl(this.itemRealm, tooltipParams) + getApiKey());
        String urlString = url.toExternalForm();

        synchronized (itemCache) {
            ItemInformation item = itemCache.get(urlString);

            if (item != null) {
                // log.info("Item Cache hit: " + server + "/" + tooltipParams);
                cacheHit++;
                return item;
            } else {
                cacheMiss++;
                // log.info("Item Cache miss: " + server + "/" + tooltipParams);
            }
        }

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        ItemInformation out = readValue(mapper, url, ItemInformation.class);

        if (out.code == null) {
            synchronized (itemCache) {
                itemCache.put(urlString, out);
            }

            newItem(out);
        }

        return out;

    }

    protected void newItem(ItemInformation item) {

    }

    public SeasonIndex readSeasonIndex(Realm realm) throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.seasonIndexUrl(realm.getApiHost()) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        SeasonIndex out = readValue(mapper, url, SeasonIndex.class);

        return out;
    }

    public Season readSeason(Realm realm, int season) throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.seasonUrl(realm.getApiHost(), season) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        Season out = readValue(mapper, url, Season.class);

        return out;
    }

    public Leaderboard readSeasonLeaderboard(Realm realm, int season, String leaderboard)
            throws JsonParseException, IOException {

        URL url = new URL(
                UrlHelper.seasonLeaderboardUrl(realm.getApiHost(), season, leaderboard) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        Leaderboard out = readValue(mapper, url, Leaderboard.class);

        return out;
    }

    public SeasonIndex readEraIndex(Realm realm) throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.eraIndexUrl(realm.getApiHost()) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        SeasonIndex out = readValue(mapper, url, SeasonIndex.class);

        return out;
    }

    public Season readEra(Realm realm, int era) throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.eraUrl(realm.getApiHost(), era) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        Season out = readValue(mapper, url, Season.class);

        return out;
    }

    public Leaderboard readEraLeaderboard(Realm realm, int era, String leaderboard)
            throws JsonParseException, IOException {

        URL url = new URL(UrlHelper.eraLeaderboardUrl(realm.getApiHost(), era, leaderboard) + getAccessToken());

        ObjectMapper mapper = new ObjectMapper();
        throttle();
        Leaderboard out = readValue(mapper, url, Leaderboard.class);

        return out;
    }

    public long getErrors() {
        return errors;
    }

    public long getRetryAttempts() {
        return retryAttempts;
    }
}